Eloquent Relations Deep — Advanced Relationship Patterns in Laravel
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
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