Relations
Miko ORM provides powerful relationship management. Define HasOne, HasMany, BelongsTo, and BelongsToMany relationships between your models.
Relationship Types Summary
| Relationship | Method | Description |
|---|---|---|
| HasOne | hasOne() | One-to-one (User → Profile) |
| HasMany | hasMany() | One-to-many (User → Orders) |
| BelongsTo | belongsTo() | Inverse relation (Order → User) |
| BelongsToMany | belongsToMany() | Many-to-many (User ↔ Roles) |
| Eager Loading | with() | Prevents N+1 query problem |
| Lazy Loading | load() | Load on demand |
HasOne - One-to-One Relationship
Used when a user has exactly one profile.
Model Definition
class User extends Model
{
protected static string $table = 'users';
protected string $primaryKey = 'Id';
/**
* Get user's profile
*/
public function profile(): HasOne
{
return $this->hasOne(Profile::class, 'UserId', 'Id');
}
}
class Profile extends Model
{
protected static string $table = 'profiles';
/**
* Get profile's owner
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'UserId', 'Id');
}
}
Usage
// Get user's profile
$user = User::find(1);
$profile = $user->profile;
echo $profile->Bio;
echo $profile->Avatar;
// Access user through profile
$profile = Profile::find(1);
$user = $profile->user;
echo $user->Name;
HasMany - One-to-Many Relationship
Used when a user has multiple orders.
Model Definition
class User extends Model
{
/**
* Get user's orders
*/
public function orders(): HasMany
{
return $this->hasMany(Order::class, 'UserId', 'Id');
}
/**
* Get user's comments
*/
public function comments(): HasMany
{
return $this->hasMany(Comment::class, 'UserId');
}
}
Usage
// Get all user's orders
$user = User::find(1);
$orders = $user->orders;
foreach ($orders as $order) {
echo $order->OrderNumber . ' - ' . $order->TotalAmount;
}
// Create new record through relationship
$user->orders()->create([
'OrderNumber' => 'ORD-001',
'TotalAmount' => 150.00,
'Status' => 'pending'
]);
// Save existing model to relationship
$order = new Order(['OrderNumber' => 'ORD-002']);
$user->orders()->save($order);
BelongsTo - Inverse Relationship
Inverse of HasOne or HasMany relationship.
Model Definition
class Order extends Model
{
protected static string $table = 'orders';
/**
* Get order's owner
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'UserId', 'Id');
}
/**
* Get order's category
*/
public function category(): BelongsTo
{
return $this->belongsTo(Category::class, 'CategoryId');
}
}
Usage
// Get order's user
$order = Order::find(1);
$user = $order->user;
echo "Order owner: " . $user->Name;
// Associate - Link to parent
$user = User::find(5);
$order->user()->associate($user);
$order->save();
// Dissociate - Remove link
$order->user()->dissociate();
$order->save();
BelongsToMany - Many-to-Many Relationship
Used for users and roles type relationships.
Database Structure
users user_roles roles
------ ---------- ------
Id UserId Id
Name RoleId Name
Model Definition
class User extends Model
{
/**
* Get user's roles
*/
public function roles(): BelongsToMany
{
return $this->belongsToMany(
Role::class, // Related model
'user_roles', // Pivot table
'UserId', // This model's foreign key
'RoleId' // Related model's foreign key
);
}
}
class Role extends Model
{
/**
* Get users with this role
*/
public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'user_roles', 'RoleId', 'UserId');
}
}
Usage
// Get user's roles
$user = User::find(1);
$roles = $user->roles;
foreach ($roles as $role) {
echo $role->Name;
}
// Attach role
$user->roles()->attach(1); // Single role
$user->roles()->attach([1, 2, 3]); // Multiple roles
// Detach role
$user->roles()->detach(1); // Single role
$user->roles()->detach([1, 2]); // Multiple roles
$user->roles()->detach(); // All roles
// Sync - Only specified roles remain
$user->roles()->sync([1, 2, 3]);
// Toggle - Attach if not exists, detach if exists
$user->roles()->toggle([1, 2]);
Eager Loading - N+1 Query Problem
The Problem
// N+1 query problem - BAD
$users = User::all();
foreach ($users as $user) {
echo $user->profile->Bio; // Separate query each loop!
}
// 1 (users) + N (profiles) = N+1 queries
Solution: with()
// With eager loading - GOOD
$users = User::with('profile')->get();
foreach ($users as $user) {
echo $user->profile->Bio; // No query, already loaded
}
// Only 2 queries: SELECT * FROM users + SELECT * FROM profiles WHERE UserId IN (...)
Multiple Relations
// Load multiple relations
$users = User::with('profile', 'orders', 'roles')->get();
// Nested relations
$users = User::with('orders.items.product')->get();
// Conditional eager loading
$users = User::with(['orders' => function($query) {
$query->where('Status', 'completed')
->orderBy('CreatedDate', 'desc');
}])->get();
Lazy Loading - load()
Load relations on already retrieved models.
$user = User::find(1);
// Load relation later
$user->load('orders');
$user->load('profile', 'roles');
// Conditional lazy loading
$user->load(['orders' => function($query) {
$query->where('TotalAmount', '>', 100);
}]);
Relationship Queries
has() - Records With Relationship
// Users with at least one order
$users = User::has('orders')->get();
// Users with more than 5 orders
$users = User::has('orders', '>', 5)->get();
// Orders with at least one comment
$orders = Order::has('comments')->get();
whereHas() - Conditional Relationship Query
// Users with completed orders
$users = User::whereHas('orders', function($query) {
$query->where('Status', 'completed');
})->get();
// Users with orders over $1000
$users = User::whereHas('orders', function($query) {
$query->where('TotalAmount', '>', 1000);
})->get();
withCount() - Relationship Count
// Get users with order count
$users = User::withCount('orders')->get();
foreach ($users as $user) {
echo $user->Name . ': ' . $user->orders_count . ' orders';
}
// Multiple relationship counts
$users = User::withCount(['orders', 'comments', 'reviews'])->get();
Complete Example: E-Commerce Models
// User.php
class User extends Model
{
use HasTimestamps, SoftDeletes;
protected static string $table = 'users';
public function profile(): HasOne
{
return $this->hasOne(Profile::class, 'UserId');
}
public function orders(): HasMany
{
return $this->hasMany(Order::class, 'UserId');
}
public function roles(): BelongsToMany
{
return $this->belongsToMany(Role::class, 'user_roles', 'UserId', 'RoleId');
}
}
// Order.php
class Order extends Model
{
public function user(): BelongsTo
{
return $this->belongsTo(User::class, 'UserId');
}
public function items(): HasMany
{
return $this->hasMany(OrderItem::class, 'OrderId');
}
}
// Usage
$user = User::with(['profile', 'orders.items.product'])->find(1);
echo "User: " . $user->Name;
echo "Profile: " . $user->profile->Bio;
foreach ($user->orders as $order) {
echo "Order: " . $order->OrderNumber;
foreach ($order->items as $item) {
echo " - " . $item->product->Name . " x " . $item->Quantity;
}
}