Usage
Defining Models
Every model extends the abstract Model class and declares at minimum a table name:
<?php
declare(strict_types=1);
namespace App\Models;
use WPZylos\Framework\Model\Model;
class Order extends Model
{
/**
* The table name without WordPress prefix.
*/
protected static string $table = 'orders';
/**
* Mass-assignable attributes.
*/
protected array $fillable = ['customer_id', 'total', 'status', 'notes'];
/**
* Attribute casts.
*/
protected array $casts = [
'customer_id' => 'integer',
'total' => 'float',
'is_paid' => 'bool',
'metadata' => 'json',
];
/**
* Attributes hidden from serialization.
*/
protected array $hidden = ['internal_notes'];
}
Model Properties
| Property | Type | Default | Description |
|---|---|---|---|
$table | string | '' | Table name without WP prefix |
$primaryKey | string | 'id' | Primary key column |
$fillable | array | [] | Mass-assignable attributes |
$guarded | array | ['id'] | Non-mass-assignable attributes |
$casts | array | [] | Attribute type casts |
$hidden | array | [] | Hidden from serialization |
$timestamps | bool | true | Auto-manage timestamps |
$createdAtColumn | ?string | 'created_at' | Created timestamp column |
$updatedAtColumn | ?string | 'updated_at' | Updated timestamp column |
CRUD Operations
Create
// Method 1: Create and persist in one step
$order = Order::create([
'customer_id' => 42,
'total' => 149.99,
'status' => 'pending',
]);
// Method 2: Create instance, then save
$order = new Order(['customer_id' => 42, 'total' => 149.99]);
$order->status = 'pending';
$order->save();
Read
// Find by primary key
$order = Order::find(1);
// Find or throw RuntimeException
$order = Order::findOrFail(1);
// Get all records
$orders = Order::all(); // Returns ModelCollection
// Query with conditions
$pending = Order::where('status', 'pending')->get();
Update
// Method 1: Update with array
$order->update(['status' => 'shipped', 'total' => 199.99]);
// Method 2: Set attributes and save
$order->status = 'shipped';
$order->save();
Delete
$order->delete();
Attribute Casting
Define $casts to automatically convert attributes on get:
protected array $casts = [
'quantity' => 'int', // also: 'integer'
'price' => 'float', // also: 'double', 'real'
'is_active' => 'bool', // also: 'boolean'
'code' => 'string',
'tags' => 'array', // JSON string → array
'metadata' => 'json', // JSON string → array
'created_at' => 'datetime', // String → DateTimeImmutable
];
Casting also works in reverse when serializing for database storage:
bool→0or1array/json→ JSON stringdatetime→Y-m-d H:i:sstring
Accessors & Mutators
Accessors
Define a getXxxAttribute method to transform a value when reading:
public function getFullNameAttribute($value): string
{
return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}
// Usage
echo $user->full_name; // "John Doe"
Mutators
Define a setXxxAttribute method to transform a value when writing:
public function setEmailAttribute($value): void
{
$this->attributes['email'] = strtolower(trim($value));
}
// Usage
$user->email = ' [email protected] ';
// Stored as: "[email protected]"
Relationships
Has One
class User extends Model
{
protected static string $table = 'users';
public function profile()
{
return $this->hasOne(Profile::class);
// Looks for: profiles.user_id = users.id
}
}
$profile = $user->profile; // Lazy-loaded, cached
Has Many
class User extends Model
{
protected static string $table = 'users';
public function posts()
{
return $this->hasMany(Post::class);
// Looks for: posts.user_id = users.id
}
}
$posts = $user->posts; // ModelCollection
Belongs To
class Post extends Model
{
protected static string $table = 'posts';
public function author()
{
return $this->belongsTo(User::class, 'author_id');
// Looks for: users.id = posts.author_id
}
}
$author = $post->author;
Custom Foreign Keys
// Has Many with custom keys
$this->hasMany(Comment::class, 'post_id', 'id');
// Belongs To with custom keys
$this->belongsTo(User::class, 'author_id', 'id');
Soft Deletes
Add the SoftDeletes trait to enable soft deletes:
use WPZylos\Framework\Model\Concerns\SoftDeletes;
class Article extends Model
{
use SoftDeletes;
protected static string $table = 'articles';
protected array $fillable = ['title', 'body'];
// Optionally customize the column name
// protected string $deletedAtColumn = 'deleted_at';
}
Usage
$article = Article::find(1);
// Soft delete
$article->delete(); // Sets deleted_at timestamp
$article->trashed(); // true
// Restore
$article->restore(); // Clears deleted_at
$article->trashed(); // false
// Force delete (permanent)
$article->forceDelete(); // Actually removes from DB
// Query scopes
Article::onlyTrashed()->get(); // Only trashed records
Article::withTrashed()->get(); // All records including trashed
Model Events
Register lifecycle event callbacks in the static boot() method:
class Order extends Model
{
protected static string $table = 'orders';
protected array $fillable = ['customer_id', 'total', 'status'];
protected static function boot(): void
{
// Before events can cancel by returning false
static::creating(function (Order $order) {
if ($order->total <= 0) {
return false; // Cancel creation
}
});
static::created(function (Order $order) {
// Send notification, log, etc.
});
static::updating(function (Order $order) {
// Validate before update
});
static::deleting(function (Order $order) {
if ($order->status === 'processing') {
return false; // Prevent deletion of processing orders
}
});
}
}
Available events: creating, created, updating, updated, deleting, deleted, saving, saved.
Query Scopes
Define reusable query constraints as scopeXxx methods:
class Order extends Model
{
protected static string $table = 'orders';
protected array $fillable = ['customer_id', 'total', 'status'];
public function scopePending($query)
{
return $query->where('status', 'pending');
}
public function scopeHighValue($query, float $min = 100.0)
{
return $query->where('total', '>=', $min);
}
}
// Usage — called as static methods
$pending = Order::pending()->get();
$bigOrders = Order::highValue(500)->get();
Timestamps
By default, models auto-manage created_at and updated_at:
// Disable timestamps
protected bool $timestamps = false;
// Customize column names
protected ?string $createdAtColumn = 'date_created';
protected ?string $updatedAtColumn = 'date_modified';
// Disable one column
protected ?string $createdAtColumn = null; // No created_at
// Touch updated_at manually
$model->touch();
Mass Assignment
Using $fillable (whitelist)
protected array $fillable = ['name', 'email', 'role'];
// Only name, email, role will be set
$user = User::create($request);
Using $guarded (blacklist)
protected array $guarded = ['id', 'is_admin'];
// Everything except id, is_admin can be set
$user = User::create($request);
Force Fill (bypass protection)
$user->forceFill(['is_admin' => true]);
Serialization
$user = User::find(1);
// To array (excludes $hidden attributes)
$array = $user->toArray();
// To JSON
$json = $user->toJson();
$json = (string) $user; // Same as toJson()
// JSON serializable
echo json_encode($user);
// Collection serialization
$users = User::all();
$array = $users->toArray();
$json = $users->toJson();
Dirty Tracking
$user = User::find(1);
$user->isDirty(); // false
$user->name = 'Updated';
$user->isDirty(); // true
$user->isDirty('name'); // true
$user->isDirty('email'); // false
$user->getDirty(); // ['name' => 'Updated']
$user->getOriginal(); // Original attributes snapshot