Relationships in Laravel Eloquent

Laravel's Eloquent ORM provides several types of database relationships that make working with related data intuitive and efficient. Here's a detailed guide to all relationship types:

1. One-to-One

A basic relationship where one model owns exactly one of another model.

php

// User has one Phone
class User extends Model
{
    public function phone()
    {
        return $this->hasOne(Phone::class);
        // Optional: specify foreign key and local key
        // return $this->hasOne(Phone::class, 'foreign_key', 'local_key');
    }
}

// Inverse relationship in Phone model
class Phone extends Model
{
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

// Usage:
$phone = User::find(1)->phone;
$user = Phone::find(1)->user;

2. One-to-Many

A relationship where one model has many of another model.

php

// Post has many Comments
class Post extends Model
{
    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

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

// Usage:
$comments = Post::find(1)->comments;
$post = Comment::find(1)->post;

// Create related model
$post->comments()->create(['body' => 'A new comment']);

3. Many-to-Many

A more complex relationship that requires an intermediate join table.

php

// User belongs to many Roles (and vice versa)
class User extends Model
{
    public function roles()
    {
        return $this->belongsToMany(Role::class);
        // With custom pivot table name:
        // return $this->belongsToMany(Role::class, 'role_user');
        // With custom keys:
        // return $this->belongsToMany(Role::class, 'role_user', 'user_id', 'role_id');
    }
}

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

// Usage:
$user = User::find(1);
foreach ($user->roles as $role) {
    //
}

// Attach/detach/sync relationships
$user->roles()->attach($roleId);
$user->roles()->detach($roleId);
$user->roles()->sync([1, 2, 3]); // Only these IDs will be attached

Pivot Tables with Extra Columns

php

// Defining the relationship with pivot columns
public function roles()
{
    return $this->belongsToMany(Role::class)->withPivot('active', 'created_by');
}

// Accessing pivot data
foreach ($user->roles as $role) {
    echo $role->pivot->active;
}

// Filtering by pivot columns
$users = User::whereHas('roles', function($query) {
    $query->where('active', true);
})->get();

4. Has-One-Through & Has-Many-Through

These define "distant" relationships through intermediate models.

php

// Supplier -> has one -> User -> has one -> History
// (Get supplier's history through user)
class Supplier extends Model
{
    public function history()
    {
        return $this->hasOneThrough(
            History::class,
            User::class,
            'supplier_id', // Foreign key on users table
            'user_id',    // Foreign key on history table
            'id',         // Local key on suppliers table
            'id'          // Local key on users table
        );
    }
}

// Country -> has many -> User -> has many -> Post
// (Get country's posts through users)
class Country extends Model
{
    public function posts()
    {
        return $this->hasManyThrough(
            Post::class,
            User::class,
            'country_id', // Foreign key on users table
            'user_id',     // Foreign key on posts table
            'id',          // Local key on countries table
            'id'           // Local key on users table
        );
    }
}

5. Polymorphic Relationships

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

One-to-One Polymorphic

php

// Image can belong to either User or Post
class Image extends Model
{
    public function imageable()
    {
        return $this->morphTo();
    }
}

class User extends Model
{
    public function image()
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}

class Post extends Model
{
    public function image()
    {
        return $this->morphOne(Image::class, 'imageable');
    }
}

// Usage:
$image = User::find(1)->image;
$imageable = $image->imageable; // Returns User or Post instance

One-to-Many Polymorphic

php

// Comment can belong to either Video or Post
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');
    }
}

Many-to-Many Polymorphic

More complex relationship with a pivot table.

php

// Post and Video can have many Tags, and Tag can belong to many Posts/Videos
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');
    }
}

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

    public function videos()
    {
        return $this->morphedByMany(Video::class, 'taggable');
    }
}

Eager Loading Relationships

To prevent the N+1 query problem:

php

// Load multiple relationships
$posts = Post::with(['comments', 'tags'])->get();

// Nested eager loading
$posts = Post::with('comments.author')->get();

// Conditional eager loading
$posts = Post::with(['comments' => function($query) {
    $query->where('approved', true);
}])->get();

// Lazy eager loading (after initial query)
$posts = Post::all();
$posts->load('comments');

Querying Relationship Existence

php

// Get all posts with at least one comment
$posts = Post::has('comments')->get();

// Get all posts with 3+ comments
$posts = Post::has('comments', '>=', 3)->get();

// More complex conditions
$posts = Post::whereHas('comments', function($query) {
    $query->where('content', 'like', '%code%');
})->get();

// Doesn't have relationship
$posts = Post::doesntHave('comments')->get();

Saving Related Models

php
// Save a single related model
$comment = new Comment(['body' => 'A new comment.']);
$post = Post::find(1);
$post->comments()->save($comment);

// Save many related models
$post->comments()->saveMany([
    new Comment(['body' => 'First comment']),
    new Comment(['body' => 'Second comment']),
]);

// Create (save and return model)
$comment = $post->comments()->create([
    'body' => 'A new comment.',
]);

// Update related models
$post->comments()->where('id', 1)->update(['body' => 'Updated']);

To Top