Examples

E-Commerce Order System

A complete example modeling an e-commerce system with orders, customers, and line items.

Models

<?php

declare(strict_types=1);

namespace App\Models;

use WPZylos\Framework\Model\Model;
use WPZylos\Framework\Model\Concerns\SoftDeletes;

class Customer extends Model
{
    protected static string $table = 'customers';

    protected array $fillable = ['name', 'email', 'phone', 'is_vip'];

    protected array $casts = [
        'is_vip' => 'bool',
    ];

    protected array $hidden = ['password_hash'];

    public function orders()
    {
        return $this->hasMany(Order::class);
    }

    // Accessor: $customer->display_name
    public function getDisplayNameAttribute($value): string
    {
        $name = $this->attributes['name'];
        return $this->attributes['is_vip'] ? "⭐ {$name}" : $name;
    }

    // Scope: Customer::vip()->get()
    public function scopeVip($query)
    {
        return $query->where('is_vip', 1);
    }
}

class Order extends Model
{
    use SoftDeletes;

    protected static string $table = 'orders';

    protected array $fillable = [
        'customer_id', 'total', 'tax', 'status', 'notes', 'shipped_at',
    ];

    protected array $casts = [
        'customer_id' => 'integer',
        'total'       => 'float',
        'tax'         => 'float',
        'shipped_at'  => 'datetime',
    ];

    public function customer()
    {
        return $this->belongsTo(Customer::class);
    }

    public function lineItems()
    {
        return $this->hasMany(LineItem::class);
    }

    // Accessor: $order->grand_total
    public function getGrandTotalAttribute($value): float
    {
        return $this->attributes['total'] + $this->attributes['tax'];
    }

    // Scopes
    public function scopePending($query)
    {
        return $query->where('status', 'pending');
    }

    public function scopeShipped($query)
    {
        return $query->where('status', 'shipped');
    }

    public function scopeHighValue($query, float $min = 100.0)
    {
        return $query->where('total', '>=', $min);
    }

    // Events
    protected static function boot(): void
    {
        static::creating(function (Order $order) {
            if ($order->total <= 0) {
                return false;
            }
        });

        static::updating(function (Order $order) {
            if ($order->isDirty('status') && $order->status === 'shipped') {
                $order->shipped_at = gmdate('Y-m-d H:i:s');
            }
        });
    }
}

class LineItem extends Model
{
    protected static string $table = 'line_items';

    protected array $fillable = ['order_id', 'product_name', 'quantity', 'unit_price'];

    protected array $casts = [
        'order_id'   => 'integer',
        'quantity'   => 'integer',
        'unit_price' => 'float',
    ];

    public function order()
    {
        return $this->belongsTo(Order::class);
    }

    // Accessor: $item->subtotal
    public function getSubtotalAttribute($value): float
    {
        return $this->attributes['quantity'] * $this->attributes['unit_price'];
    }
}

Usage

// Create a customer
$customer = Customer::create([
    'name'   => 'Jane Smith',
    'email'  => '[email protected]',
    'is_vip' => true,
]);

echo $customer->display_name; // "⭐ Jane Smith"

// Create an order
$order = Order::create([
    'customer_id' => $customer->getKey(),
    'total'       => 249.99,
    'tax'         => 20.00,
    'status'      => 'pending',
]);

echo $order->grand_total; // 269.99

// Query with scopes
$pendingOrders = Order::pending()->get();
$bigOrders     = Order::highValue(500)->get();
$vipCustomers  = Customer::vip()->get();

// Relationships
$customer = $order->customer;
$orders   = $customer->orders;
$items    = $order->lineItems;

// Soft delete and restore
$order->delete();           // Soft delete
$order->trashed();          // true
$order->restore();          // Restore
$order->forceDelete();      // Permanently delete

// Collections
$orderTotals = $customer->orders->pluck('total');
$highOrders  = $customer->orders->filter(fn($o) => $o->total > 100);
$customer->orders->each(function ($order) {
    echo "Order #{$order->getKey()}: \${$order->total}\n";
});

Blog System

<?php

declare(strict_types=1);

namespace App\Models;

use WPZylos\Framework\Model\Model;
use WPZylos\Framework\Model\Concerns\SoftDeletes;

class Post extends Model
{
    use SoftDeletes;

    protected static string $table = 'blog_posts';

    protected array $fillable = ['title', 'slug', 'body', 'author_id', 'status', 'published_at'];

    protected array $casts = [
        'author_id'    => 'integer',
        'published_at' => 'datetime',
        'view_count'   => 'integer',
    ];

    public function author()
    {
        return $this->belongsTo(Author::class);
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    // Mutator: auto-generate slug
    public function setTitleAttribute($value): void
    {
        $this->attributes['title'] = $value;
        $this->attributes['slug'] = sanitize_title($value);
    }

    public function scopePublished($query)
    {
        return $query->where('status', 'published');
    }

    public function scopeDraft($query)
    {
        return $query->where('status', 'draft');
    }

    protected static function boot(): void
    {
        static::creating(function (Post $post) {
            if (empty($post->slug)) {
                $post->slug = sanitize_title($post->title);
            }
        });
    }
}

class Comment extends Model
{
    protected static string $table = 'blog_comments';

    protected array $fillable = ['post_id', 'author_name', 'email', 'body', 'is_approved'];

    protected array $casts = [
        'post_id'     => 'integer',
        'is_approved' => 'bool',
    ];

    public function post()
    {
        return $this->belongsTo(Post::class);
    }

    public function scopeApproved($query)
    {
        return $query->where('is_approved', 1);
    }
}

Blog Usage

// Create a post
$post = Post::create([
    'title'     => 'Getting Started with WPZylos Model',
    'body'      => 'The Model package provides an elegant...',
    'author_id' => 1,
    'status'    => 'draft',
]);

// slug is auto-generated: "getting-started-with-wpzylos-model"
echo $post->slug;

// Publish
$post->update(['status' => 'published', 'published_at' => gmdate('Y-m-d H:i:s')]);

// Get published posts
$published = Post::published()->get();

// Get post with comments
$post = Post::find(1);
$comments = $post->comments;
$approvedComments = $comments->filter(fn($c) => $c->is_approved);

// Serialize for API response
echo json_encode([
    'post'     => $post->toArray(),
    'comments' => $approvedComments->toArray(),
]);

Settings / Key-Value Store

class Setting extends Model
{
    protected static string $table = 'settings';
    protected static string $primaryKey = 'key';

    protected array $fillable = ['key', 'value', 'group'];

    protected array $casts = [
        'value' => 'json',
    ];

    protected bool $timestamps = false;

    public function scopeGroup($query, string $group)
    {
        return $query->where('group', $group);
    }

    public static function get(string $key, mixed $default = null): mixed
    {
        $setting = static::find($key);
        return $setting?->value ?? $default;
    }

    public static function set(string $key, mixed $value, string $group = 'general'): static
    {
        $setting = static::find($key);

        if ($setting) {
            $setting->update(['value' => $value]);
            return $setting;
        }

        return static::create([
            'key'   => $key,
            'value' => $value,
            'group' => $group,
        ]);
    }
}

// Usage
Setting::set('site_name', 'My WP Site');
Setting::set('smtp_config', ['host' => 'smtp.example.com', 'port' => 587], 'email');

$name = Setting::get('site_name'); // "My WP Site"
$emailSettings = Setting::group('email')->get();