Has-many-through and has-one-through
Concept
hasManyThrough and hasOneThrough allow accessing distant relationships through an intermediate model without defining the intermediate manually.
hasManyThrough($final, $through): Access records from a model that doesn't have a direct foreign key link. Example: Country hasMany Users hasMany Posts → Country hasManyThrough Posts (via Users).
hasOneThrough: Same but returns a single record.
Polymorphic relationships allow a model to belong to multiple different models using a single association. This avoids creating multiple foreign key columns or join tables.
morphTo / morphOne / morphMany: The polymorphic side has two database columns: *_type (class name) and *_id (foreign key). A Comment model can belong to either a Post or a Video using commentable_type + commentable_id.
Morph maps: Replace class names with short aliases in the database. Relation::morphMap(['post' => Post::class, 'video' => Video::class]). Defined in AppServiceProvider. This decouples the DB from class names — important for refactoring.
morphToMany / morphedByMany: Polymorphic many-to-many. A Tag can belong to both Post and Video via a single taggables pivot table.
Code Example
<?php
// hasManyThrough — Country → Users → Posts
class Country extends Model
{
// Country hasManyThrough Posts via Users
// posts.user_id → users.id; users.country_id → countries.id
public function posts(): \Illuminate\Database\Eloquent\Relations\HasManyThrough
{
return $this->hasManyThrough(Post::class, User::class);
}
}
// $country->posts — all posts from all users in that country
// Polymorphic one-to-many — Comment can belong to Post OR Video
class Comment extends Model
{
// commentable_type = 'App\Models\Post' or 'App\Models\Video'
// commentable_id = the post_id or video_id
public function commentable(): \Illuminate\Database\Eloquent\Relations\MorphTo
{
return $this->morphTo(); // infers columns: commentable_type, commentable_id
}
}
class Post extends Model
{
public function comments(): \Illuminate\Database\Eloquent\Relations\MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}
class Video extends Model
{
public function comments(): \Illuminate\Database\Eloquent\Relations\MorphMany
{
return $this->morphMany(Comment::class, 'commentable');
}
}
// Usage
$post = Post::find(1);
$post->comments()->create(['body' => 'Great article!', 'user_id' => 1]);
$comment = Comment::find(1);
$parent = $comment->commentable; // returns Post or Video instance
// Morph map — in AppServiceProvider::boot()
\Illuminate\Database\Eloquent\Relations\Relation::morphMap([
'post' => \App\Models\Post::class,
'video' => \App\Models\Video::class,
]);
// Now commentable_type stores 'post' instead of 'App\Models\Post'
// Polymorphic many-to-many — Tags on both Posts and Videos
class Tag extends Model
{
public function posts(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
public function videos(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphedByMany(Video::class, 'taggable');
}
}
class Post extends Model
{
public function tags(): \Illuminate\Database\Eloquent\Relations\MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}