Skip to content

Eloquent Relations Deep — Advanced Relationship Patterns in Laravel

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Eloquent Relations Deep. We cover key concepts, practical examples, and best practices to help you master this topic.

Laravel Eloquent supports advanced relationship patterns including has-many-through, polymorphic relations, many-to-many with pivot data, and custom pivot models.

What You'll Learn

By the end of this tutorial, you'll implement hasManyThrough for nested relations, morphMany for polymorphic comments, morphToMany for tags on multiple models, and optimize relation queries.

Why Advanced Relations Matter

Complex domain models need more than basic OneToMany. Polymorphic relations allow multiple models to share the same relation type, reducing table duplication.

Real-World Use

A social platform uses morphMany for comments on posts, photos, and videos. A blog uses morphToMany to tag both articles and products, with pivot data for tag metadata.

Relations Path

flowchart LR
  A[Eloquent ORM] --> B[Relations Deep]
  B --> C[hasManyThrough]
  B --> D[Polymorphic]
  B --> E[Pivot Data]
  B --> F[Custom Pivot]
  B --> G{You Are Here}
  style G fill:#f90,color:#fff

HasManyThrough

Access nested relations through an intermediate model.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
class Country extends Model {
    public function posts(): HasManyThrough {
        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
        );
    }
}
// Usage: Country::find(1)->posts

Polymorphic OneToMany

One relation for comments across multiple models.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphMany;
class Post extends Model {
    public function comments(): MorphMany {
        return $this->morphMany(Comment::class, "commentable");
    }
}
class Video extends Model {
    public function comments(): MorphMany {
        return $this->morphMany(Comment::class, "commentable");
    }
}
class Comment extends Model {
    public function commentable(): MorphTo {
        return $this->morphTo();
    }
}
// Comment table: id, body, commentable_id, commentable_type

Polymorphic ManyToMany

Tags that can apply to multiple model types.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
class Post extends Model {
    public function tags(): MorphToMany {
        return $this->morphToMany(Tag::class, "taggable");
    }
}
class Video extends Model {
    public function tags(): MorphToMany {
        return $this->morphToMany(Tag::class, "taggable");
    }
}
class Tag extends Model {
    public function posts(): MorphToMany {
        return $this->morphedByMany(Post::class, "taggable");
    }
    public function videos(): MorphToMany {
        return $this->morphedByMany(Video::class, "taggable");
    }
}
// taggables table: tag_id, taggable_id, taggable_type

Pivot Data

Working with extra columns on pivot tables.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class User extends Model {
    public function roles(): BelongsToMany {
        return $this->belongsToMany(Role::class)
            ->withPivot("assigned_at", "expires_at")
            ->withTimestamps()
            ->wherePivot("active", true);
    }
}
// Usage
$user->roles()->attach($roleId, ["assigned_at" => now()]);
foreach ($user->roles as $role) {
    echo $role->pivot->assigned_at;
    echo $role->pivot->expires_at;
}

Custom Pivot Models

Extend Pivot model for custom behavior on pivot tables.

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class RoleUser extends Pivot {
    protected $casts = ["expires_at" => "datetime"];
    protected $appends = ["is_active"];
    public function getIsActiveAttribute(): bool {
        return !$this->expires_at || $this->expires_at->isFuture();
    }
}
class User extends Model {
    public function roles(): BelongsToMany {
        return $this->belongsToMany(Role::class)
            ->using(RoleUser::class)
            ->withPivot("assigned_at", "expires_at");
    }
}

Common Mistakes

1. Not Indexing Polymorphic Columns

Without indexes on (commentable_id, commentable_type), polymorphic queries are slow.

2. Forgetting withPivot on ManyToMany

Extra pivot columns are not loaded unless specified with withPivot().

3. Overusing Polymorphic Relations

Polymorphic relations are flexible but complex. Use them only when genuinely needed.

4. Not Using withDefault for Null Relations

Use withDefault() to prevent null checks when accessing optional relations.

5. Missing Cascade Deletes on Polymorphic

Polymorphic child records are not auto-deleted. Use model events to clean up.

Practice Questions

1. What is hasManyThrough used for?

Accessing models through an intermediate model, like posts through users in a country.

2. What is the difference between morphMany and morphToMany?

morphMany is OneToMany polymorphic. morphToMany is ManyToMany polymorphic.

3. How do you add pivot data to a relation?

Chain withPivot("column1", "column2") on the belongsToMany definition.

4. What is a custom pivot model?

A class extending Pivot that adds methods, casts, and accessors for pivot tables.

5. Challenge: Create polymorphic comments with voting.

<?php
class Vote extends Model {
    public function voteable(): MorphTo {
        return $this->morphTo();
    }
}
// Add votes() morphMany to Post, Comment, Video models

FAQ

What is the morph map for?

Mapping class names to short strings in the type column for performance and readability.

How do I eager load morphMany relations?

Use with('comments') - Eloquent handles polymorphic eager loading.

Can I query pivot table data with conditions?

Yes. Use wherePivot('column', 'value') on the relation.

What is a belongsToMany vs morphToMany?

belongsToMany is for many-to-many on two models. morphToMany extends this to any model.

How do I add a custom timestamp to pivot?

Use withTimestamps() on belongsToMany.

Mini Project: Polymorphic Comment System

Build a complete polymorphic comment system.

<?php
trait Commentable {
    public function comments(): MorphMany {
        return $this->morphMany(Comment::class, "commentable");
    }
}
class Post extends Model { use Commentable; }
class Video extends Model { use Commentable; }
class Comment extends Model {
    protected $fillable = ["body", "user_id"];
    public function commentable(): MorphTo {
        return $this->morphTo();
    }
}

What's Next

Eloquent Query Scopes Eloquent Accessors Mutators Eloquent Serialization

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro