Eloquent Has Many Through: Distant Relations
Eloquent Has Many Through: Distant Relations
Eloquent Has Many Through: Distant Relations
Tip: hasManyThrough() Basics
class Country extends Model
{
public function posts(): HasManyThrough
{
return $this->hasManyThrough(Post::class, User::class);
}
}
Country → Users → Posts. Get all posts by users in a country.
Gotcha: Table Name Inference
Laravel infers table names from model names. If your tables don't follow conventions, specify them:
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
);
Tip: hasManyThrough() with Constraints
$country->posts()->where('published', true)->get();
Scopes work on through relations.
Gotcha: No Eager Loading Optimization
hasManyThrough() still generates a JOIN. For large datasets, consider a denormalized approach.
Tip: Many-to-Many Through
No built-in support. Use a package or raw query for complex multi-level many-to-many.
Gotcha: hasThrough() Doesn't Exist
Use whereHas() on the through relation instead.
Tip: Use cursor() for Memory-Neutral Iteration
When exporting 100K rows, get() loads everything into memory. cursor() uses yield and keeps memory flat regardless of row count. Perfect for artisan commands.
Tip: Pivot Data Is Read-Only by Default
Many-to-many pivot columns are read-only unless you define custom pivot models. Use ->withPivot('expires_at') to make them accessible, and newPivot() to make them writable.
Gotcha: withCount() Adds a Subquery
withCount('comments') runs a correlated subquery on every row. On large tables, this can be slower than a separate query. Profile before relying on it.
Senior Insight
The hasManyThrough relationship is powerful but often the wrong tool. I've seen it used to create three-table joins that would be clearer as explicit query builder joins. The issue: hasManyThrough hides the intermediate table, making the generated SQL non-obvious. When debugging slow queries, developers overlook the intermediate table because it's invisible in the relationship definition. I use hasManyThrough only for simple reporting queries, never for critical data operations.
Source: Laravel Docs (https://laravel.com/docs/eloquent), Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/eloquent)