Skip to content

Model Relationships

Relationships allow you to define connections between different models, making it easy to work with related data in your database.

One to One

A one-to-one relationship links one record to exactly one other record.

Defining the Relationship

php
class User extends Model
{
    public function profile()
    {
        return $this->hasOne(Profile::class);
    }
}

class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

Custom Foreign Keys

php
class User extends Model
{
    public function profile()
    {
        return $this->hasOne(Profile::class, 'user_id', 'id');
    }
}

class Profile extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class, 'user_id', 'id');
    }
}

Using the Relationship

php
$user = User::find(1);
$profile = $user->profile; // Get the user's profile

$profile = Profile::find(1);
$user = $profile->user; // Get the profile's user

// Check if relationship exists
if ($user->profile) {
    echo $user->profile->bio;
}

// Create related model
$user = User::find(1);
$profile = $user->profile()->create([
    'bio' => 'Software developer',
    'location' => 'New York'
]);

One to Many

A one-to-many relationship links one record to multiple other records.

Defining the Relationship

php
class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
    
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

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

Using the Relationship

php
$user = User::find(1);
$posts = $user->posts; // Get all user's posts

foreach ($posts as $post) {
    echo $post->title;
}

// Get posts with additional constraints
$publishedPosts = $user->posts()->where('status', 'published')->get();

// Count related models
$postCount = $user->posts()->count();

// Create related model
$user = User::find(1);
$post = $user->posts()->create([
    'title' => 'New Post',
    'content' => 'Post content...',
    'status' => 'published'
]);

// Associate existing model
$post = Post::find(1);
$user = User::find(2);
$user->posts()->save($post);

Many to Many

A many-to-many relationship allows multiple records to be related to multiple other records through a pivot table.

Defining the Relationship

php
class User extends Model
{
    public function roles()
    {
        return $this->belongsToMany(Role::class);
    }
}

class Role extends Model
{
    public function users()
    {
        return $this->belongsToMany(User::class);
    }
}

// With custom table and column names
class User extends Model
{
    public function roles()
    {
        return $this->belongsToMany(
            Role::class,
            'user_roles', // Pivot table name
            'user_id',    // Foreign key on pivot table for current model
            'role_id'     // Foreign key on pivot table for related model
        );
    }
}

Using the Relationship

php
$user = User::find(1);
$roles = $user->roles; // Get all user's roles

// Attach roles to user
$user->roles()->attach([1, 2, 3]);
$user->roles()->attach(1, ['expires_at' => '2024-12-31']); // With pivot data

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

// Sync roles (attach new, detach removed)
$user->roles()->sync([1, 2, 3]);

// Toggle roles
$user->roles()->toggle([1, 2]);

// Check if user has role
if ($user->roles()->where('name', 'admin')->exists()) {
    echo 'User is admin';
}

Pivot Table Data

php
class User extends Model
{
    public function roles()
    {
        return $this->belongsToMany(Role::class)
            ->withPivot(['expires_at', 'granted_by'])
            ->withTimestamps();
    }
}

// Access pivot data
$user = User::find(1);
foreach ($user->roles as $role) {
    echo $role->name;
    echo $role->pivot->expires_at;
    echo $role->pivot->granted_by;
}

// Update pivot data
$user->roles()->updateExistingPivot(1, ['expires_at' => '2025-12-31']);

Has Many Through

Access distant relationships through intermediate models.

php
class Country extends Model
{
    public function users()
    {
        return $this->hasMany(User::class);
    }
    
    public function posts()
    {
        return $this->hasManyThrough(Post::class, User::class);
    }
}

class User extends Model
{
    public function country()
    {
        return $this->belongsTo(Country::class);
    }
    
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// Usage
$country = Country::find(1);
$posts = $country->posts; // All posts from users in this country

Polymorphic Relationships

Allow a model to belong to more than one other model on a single association.

One to Many Polymorphic

php
class Comment extends Model
{
    public function commentable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

class Video extends Model
{
    public function comments()
    {
        return $this->morphMany(Comment::class, 'commentable');
    }
}

Using Polymorphic Relationships

php
// Create comments for different models
$post = Post::find(1);
$post->comments()->create(['content' => 'Great post!']);

$video = Video::find(1);
$video->comments()->create(['content' => 'Nice video!']);

// Access the parent model
$comment = Comment::find(1);
$commentable = $comment->commentable; // Could be Post or Video

// Check the type
if ($comment->commentable_type === 'App\\Models\\Post') {
    // It's a post comment
}

Many to Many Polymorphic

php
class Tag extends Model
{
    public function posts()
    {
        return $this->morphedByMany(Post::class, 'taggable');
    }
    
    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}

class Post extends Model
{
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

class Video extends Model
{
    public function tags()
    {
        return $this->morphToMany(Tag::class, 'taggable');
    }
}

Eager Loading

Prevent N+1 query problems by loading relationships efficiently.

Basic Eager Loading

php
// N+1 problem (bad)
$users = User::all();
foreach ($users as $user) {
    echo $user->profile->bio; // Executes a query for each user
}

// Eager loading (good)
$users = User::with('profile')->get();
foreach ($users as $user) {
    echo $user->profile->bio; // Profile already loaded
}

Multiple Relationships

php
// Load multiple relationships
$users = User::with(['posts', 'profile', 'roles'])->get();

// Nested relationships
$users = User::with('posts.comments')->get();

// Mixed relationships
$users = User::with(['posts.comments.author', 'profile'])->get();

Conditional Eager Loading

php
// Load posts with conditions
$users = User::with(['posts' => function ($query) {
    $query->where('status', 'published')
          ->orderBy('created_at', 'desc');
}])->get();

// Load multiple relationships with conditions
$users = User::with([
    'posts' => function ($query) {
        $query->where('status', 'published');
    },
    'comments' => function ($query) {
        $query->where('approved', true);
    }
])->get();

Lazy Eager Loading

Load relationships after the model is retrieved:

php
$users = User::all();

// Later, load relationships
$users->load('posts');
$users->load(['posts.comments', 'profile']);

// With conditions
$users->load(['posts' => function ($query) {
    $query->where('status', 'published');
}]);
php
// Count related models
$users = User::withCount('posts')->get();
foreach ($users as $user) {
    echo $user->posts_count;
}

// Multiple counts
$users = User::withCount(['posts', 'comments'])->get();

// Conditional counts
$users = User::withCount([
    'posts',
    'posts as published_posts_count' => function ($query) {
        $query->where('status', 'published');
    }
])->get();

// Average, sum, max, min
$users = User::withAvg('posts', 'views')->get();
$users = User::withSum('orders', 'amount')->get();

Querying Relationships

Has Queries

php
// Users who have posts
$users = User::has('posts')->get();

// Users who have at least 3 posts
$users = User::has('posts', '>=', 3)->get();

// Users who have published posts
$users = User::whereHas('posts', function ($query) {
    $query->where('status', 'published');
})->get();

// Users who don't have posts
$users = User::doesntHave('posts')->get();

// Users who don't have published posts
$users = User::whereDoesntHave('posts', function ($query) {
    $query->where('status', 'published');
})->get();

Relationship Queries

php
// Query through relationships
$posts = Post::whereRelation('user', 'status', 'active')->get();

// Or using joins
$posts = Post::join('users', 'posts.user_id', '=', 'users.id')
    ->where('users.status', 'active')
    ->select('posts.*')
    ->get();

WordPress Integration

WordPress User Relationships

php
class User extends Model
{
    public function wordpressUser()
    {
        return $this->hasOne(WP_User::class, 'ID', 'wp_user_id');
    }
    
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
    
    // Get WordPress posts
    public function wordpressPosts()
    {
        $wp_user = get_user_by('id', $this->wp_user_id);
        if ($wp_user) {
            return get_posts([
                'author' => $this->wp_user_id,
                'post_status' => 'any',
                'numberposts' => -1
            ]);
        }
        return [];
    }
}

WordPress Post Relationships

php
class Post extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
    
    // Get WordPress post
    public function wordpressPost()
    {
        return get_post($this->wp_post_id);
    }
    
    // Get post meta
    public function getMeta($key, $single = true)
    {
        return get_post_meta($this->wp_post_id, $key, $single);
    }
    
    // WordPress categories (taxonomy)
    public function categories()
    {
        $wp_post = $this->wordpressPost();
        if ($wp_post) {
            return wp_get_post_categories($this->wp_post_id, ['fields' => 'all']);
        }
        return [];
    }
}

Custom WordPress Relationships

php
class Product extends Model
{
    // Related WordPress posts
    public function relatedPosts()
    {
        $related_ids = $this->getMeta('related_post_ids');
        if ($related_ids) {
            return get_posts([
                'include' => $related_ids,
                'post_type' => 'post',
                'post_status' => 'publish'
            ]);
        }
        return [];
    }
    
    // WooCommerce integration
    public function woocommerceProduct()
    {
        if ($this->wc_product_id) {
            return wc_get_product($this->wc_product_id);
        }
        return null;
    }
}

Performance Tips

Optimize Eager Loading

php
// Load only needed columns
$users = User::with(['posts:id,user_id,title,status'])->get();

// Use select to limit columns on main model too
$users = User::select(['id', 'name', 'email'])
    ->with(['posts:id,user_id,title'])
    ->get();

Use Constraints Wisely

php
// Instead of loading all posts and filtering in PHP
$users = User::with('posts')->get();
$publishedPosts = $users->flatMap(function ($user) {
    return $user->posts->where('status', 'published');
});

// Load only published posts
$users = User::with(['posts' => function ($query) {
    $query->where('status', 'published');
}])->get();

Consider Chunking

php
// For large datasets, use chunking with relationships
User::with('posts')->chunk(100, function ($users) {
    foreach ($users as $user) {
        // Process user and their posts
    }
});

Released under the MIT License.