/ Relations Examples

Relations Examples

Complete examples of using Miko ORM relationships in real-world scenarios.


E-Commerce Model Structure

Database Schema

users                orders              order_items
------               ------              -----------
Id                   Id                  Id
Name                 UserId (FK)         OrderId (FK)
Email                OrderNumber         ProductId (FK)
                     TotalAmount         Quantity
                     Status              UnitPrice

products             categories          user_roles (pivot)
--------             ----------          ------------------
Id                   Id                  UserId (FK)
Name                 Name                RoleId (FK)
Price                ParentId (FK)
CategoryId (FK)

roles
-----
Id
Name

Model Definitions

User Model

<?php
namespace App\Models;

use Miko\Database\ORM\Model;
use Miko\Database\ORM\Relations\{HasOne, HasMany, BelongsToMany};
use Miko\Database\ORM\Traits\{HasTimestamps, SoftDeletes};

class User extends Model
{
    use HasTimestamps, SoftDeletes;
    
    protected static string $table = 'users';
    protected string $primaryKey = 'Id';
    
    protected array $fillable = ['Name', 'Email', 'Password'];
    protected array $hidden = ['Password'];
    
    /**
     * User's profile (one-to-one)
     */
    public function profile(): HasOne
    {
        return $this->hasOne(Profile::class, 'UserId');
    }
    
    /**
     * User's orders (one-to-many)
     */
    public function orders(): HasMany
    {
        return $this->hasMany(Order::class, 'UserId');
    }
    
    /**
     * User's roles (many-to-many)
     */
    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class, 'user_roles', 'UserId', 'RoleId');
    }
    
    /**
     * User's addresses (one-to-many)
     */
    public function addresses(): HasMany
    {
        return $this->hasMany(Address::class, 'UserId');
    }
    
    /**
     * Check if user has specific role
     */
    public function hasRole(string $roleName): bool
    {
        foreach ($this->roles as $role) {
            if ($role->Name === $roleName) {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Get completed orders
     */
    public function completedOrders(): HasMany
    {
        return $this->hasMany(Order::class, 'UserId')
            ->where('Status', 'completed');
    }
}

Order Model

<?php
namespace App\Models;

use Miko\Database\ORM\Model;
use Miko\Database\ORM\Relations\{BelongsTo, HasMany};
use Miko\Database\ORM\Traits\HasTimestamps;

class Order extends Model
{
    use HasTimestamps;
    
    protected static string $table = 'orders';
    
    protected array $fillable = [
        'UserId', 'OrderNumber', 'TotalAmount', 'Status', 'AddressId'
    ];
    
    /**
     * Order's owner
     */
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class, 'UserId');
    }
    
    /**
     * Order items
     */
    public function items(): HasMany
    {
        return $this->hasMany(OrderItem::class, 'OrderId');
    }
    
    /**
     * Shipping address
     */
    public function address(): BelongsTo
    {
        return $this->belongsTo(Address::class, 'AddressId');
    }
    
    /**
     * Calculate total from items
     */
    public function calculateTotal(): float
    {
        $total = 0;
        foreach ($this->items as $item) {
            $total += $item->Quantity * $item->UnitPrice;
        }
        return $total;
    }
}

OrderItem Model

<?php
namespace App\Models;

use Miko\Database\ORM\Model;
use Miko\Database\ORM\Relations\BelongsTo;

class OrderItem extends Model
{
    protected static string $table = 'order_items';
    
    protected array $fillable = [
        'OrderId', 'ProductId', 'Quantity', 'UnitPrice'
    ];
    
    /**
     * Parent order
     */
    public function order(): BelongsTo
    {
        return $this->belongsTo(Order::class, 'OrderId');
    }
    
    /**
     * Product
     */
    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class, 'ProductId');
    }
    
    /**
     * Get line total
     */
    public function getTotal(): float
    {
        return $this->Quantity * $this->UnitPrice;
    }
}

Product Model

<?php
namespace App\Models;

use Miko\Database\ORM\Model;
use Miko\Database\ORM\Relations\{BelongsTo, HasMany};

class Product extends Model
{
    protected static string $table = 'products';
    
    protected array $fillable = [
        'Name', 'Price', 'Stock', 'CategoryId', 'Description'
    ];
    
    /**
     * Product's category
     */
    public function category(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'CategoryId');
    }
    
    /**
     * Product images
     */
    public function images(): HasMany
    {
        return $this->hasMany(ProductImage::class, 'ProductId');
    }
    
    /**
     * Order items containing this product
     */
    public function orderItems(): HasMany
    {
        return $this->hasMany(OrderItem::class, 'ProductId');
    }
}

Category Model (Self-Referencing)

<?php
namespace App\Models;

use Miko\Database\ORM\Model;
use Miko\Database\ORM\Relations\{BelongsTo, HasMany};

class Category extends Model
{
    protected static string $table = 'categories';
    
    protected array $fillable = ['Name', 'ParentId'];
    
    /**
     * Parent category
     */
    public function parent(): BelongsTo
    {
        return $this->belongsTo(Category::class, 'ParentId');
    }
    
    /**
     * Child categories
     */
    public function children(): HasMany
    {
        return $this->hasMany(Category::class, 'ParentId');
    }
    
    /**
     * Products in this category
     */
    public function products(): HasMany
    {
        return $this->hasMany(Product::class, 'CategoryId');
    }
    
    /**
     * Get all descendants (recursive)
     */
    public function getAllChildren(): array
    {
        $children = [];
        foreach ($this->children as $child) {
            $children[] = $child;
            $children = array_merge($children, $child->getAllChildren());
        }
        return $children;
    }
}

Usage Examples

Creating Orders with Items

// Create order for user
$user = User::find(1);

$order = $user->orders()->create([
    'OrderNumber' => 'ORD-' . time(),
    'Status' => 'pending',
    'TotalAmount' => 0
]);

// Add items
$products = [
    ['id' => 1, 'quantity' => 2],
    ['id' => 3, 'quantity' => 1],
    ['id' => 5, 'quantity' => 3]
];

$total = 0;
foreach ($products as $item) {
    $product = Product::find($item['id']);
    
    $order->items()->create([
        'ProductId' => $product->Id,
        'Quantity' => $item['quantity'],
        'UnitPrice' => $product->Price
    ]);
    
    $total += $product->Price * $item['quantity'];
}

// Update total
$order->TotalAmount = $total;
$order->save();

Eager Loading for Performance

// Load user with all related data
$user = User::with([
    'profile',
    'roles',
    'orders.items.product',
    'addresses'
])->find(1);

// No additional queries needed
echo "User: " . $user->Name;
echo "Profile: " . $user->profile->Bio;

foreach ($user->roles as $role) {
    echo "Role: " . $role->Name;
}

foreach ($user->orders as $order) {
    echo "Order: " . $order->OrderNumber;
    
    foreach ($order->items as $item) {
        echo "  - " . $item->product->Name . " x " . $item->Quantity;
    }
}

Querying Through Relations

// Users with completed orders over $100
$users = User::whereHas('orders', function($query) {
    $query->where('Status', 'completed')
          ->where('TotalAmount', '>', 100);
})->get();

// Products in specific category
$electronics = Category::where('Name', 'Electronics')->first();
$products = $electronics->products()
    ->where('Stock', '>', 0)
    ->orderBy('Price')
    ->get();

// Users with admin role
$admins = User::whereHas('roles', function($query) {
    $query->where('Name', 'admin');
})->get();

Managing Many-to-Many

$user = User::find(1);

// Assign roles
$user->roles()->attach(1);  // Add single role
$user->roles()->attach([1, 2, 3]);  // Add multiple

// Remove roles
$user->roles()->detach(1);  // Remove single
$user->roles()->detach([1, 2]);  // Remove multiple
$user->roles()->detach();  // Remove all

// Sync roles (replace all with specified)
$user->roles()->sync([2, 3, 4]);

// Toggle (add if missing, remove if exists)
$user->roles()->toggle([1, 2]);

Order History with Statistics

$user = User::withCount('orders')
    ->with(['orders' => function($query) {
        $query->orderBy('CreatedDate', 'desc')->take(5);
    }])
    ->find(1);

echo "Total orders: " . $user->orders_count;
echo "Recent orders:";

foreach ($user->orders as $order) {
    echo $order->OrderNumber . " - $" . $order->TotalAmount;
}

// Calculate total spent
$totalSpent = $user->orders()
    ->where('Status', 'completed')
    ->sum('TotalAmount');

echo "Total spent: $" . $totalSpent;

Category Tree Navigation

// Get root categories
$rootCategories = Category::whereNull('ParentId')->get();

// Build category tree
function buildTree($categories, $parentId = null): array
{
    $tree = [];
    
    foreach ($categories as $category) {
        if ($category->ParentId == $parentId) {
            $children = buildTree($categories, $category->Id);
            
            $tree[] = [
                'id' => $category->Id,
                'name' => $category->Name,
                'children' => $children
            ];
        }
    }
    
    return $tree;
}

$allCategories = Category::all();
$tree = buildTree($allCategories);

// Get breadcrumb for category
function getBreadcrumb(Category $category): array
{
    $breadcrumb = [$category];
    
    while ($category->parent) {
        $category = $category->parent;
        array_unshift($breadcrumb, $category);
    }
    
    return $breadcrumb;
}

$category = Category::find(5);
$breadcrumb = getBreadcrumb($category);
// [Electronics, Computers, Laptops]

API Response Example

class OrderController
{
    public function show(int $id): void
    {
        $order = Order::with([
            'user:Id,Name,Email',
            'items.product:Id,Name,Price',
            'address'
        ])->find($id);
        
        if (!$order) {
            JsonResponse::notFound('Order not found');
            return;
        }
        
        JsonResponse::success([
            'order' => [
                'id' => $order->Id,
                'order_number' => $order->OrderNumber,
                'status' => $order->Status,
                'total_amount' => $order->TotalAmount,
                'created_at' => $order->CreatedDate,
                'customer' => [
                    'id' => $order->user->Id,
                    'name' => $order->user->Name,
                    'email' => $order->user->Email
                ],
                'shipping_address' => $order->address ? [
                    'street' => $order->address->Street,
                    'city' => $order->address->City,
                    'country' => $order->address->Country
                ] : null,
                'items' => array_map(function($item) {
                    return [
                        'product_id' => $item->ProductId,
                        'product_name' => $item->product->Name,
                        'quantity' => $item->Quantity,
                        'unit_price' => $item->UnitPrice,
                        'total' => $item->Quantity * $item->UnitPrice
                    ];
                }, $order->items)
            ]
        ]);
    }
}