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)
]
]);
}
}