Eloquent Morph To Many: Polymorphic Relations
Eloquent Morph To Many: Polymorphic Relations
Eloquent Morph To Many: Polymorphic Relations
Tip: Define MorphToMany
class Tag extends Model
{
public function posts(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
}
Gotcha: Inverse Relation
class Post extends Model
{
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
Tip: Attach Tags
$post->tags()->attach([1, 2, 3]);
Gotcha: Pivot Table Structure
taggables: tag_id, taggable_id, taggable_type
Tip: Query by Tag
Post::whereHas('tags', fn($q) => $q->where('name', 'laravel'))->get();
Gotcha: Morph Map with Many-to-Many
Register morph map for cleaner type names in the pivot table.
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: whereHas() vs load() — Two Different Things
whereHas() filters the parent query by relationship existence. load() eager-loads relationships AFTER the query. Mixing them up is a common source of logic bugs.
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
Eloquent relationships are elegant, but they hide database queries that can silently destroy performance. I once optimized an API endpoint by replacing 47 lazy-loaded relationship calls with two eager-loaded with() statements — response time dropped from 12 seconds to 200ms. The Laravel ecosystem has Model::preventLazyLoading(), and I always enable it in local and staging environments. If a relationship isn't eager-loaded, you'll know immediately.
Source: Laravel Docs (https://laravel.com/docs/eloquent), Laravel News (https://laravel-news.com/), Freek.dev (https://freek.dev/tags/eloquent)