Mastering Eloquent ORM in Laravel: Advanced Relationships and Tips
Introduction
When building robust applications with Laravel, Eloquent ORM is one of the most powerful tools in your arsenal. It abstracts complex SQL queries behind simple PHP methods and helps maintain clean, readable code. While basic relationships such as hasOne, hasMany, belongsTo, and belongsToMany are commonly used, Eloquent also provides advanced relationship features to model even the most complex data structures.
In this blog, we will deep dive into advanced Eloquent relationships, performance optimization techniques, and pro tips that will take your Laravel application to the next level.
Table of Contents
- What is Eloquent ORM?
- Basic Recap of Eloquent Relationships
- Advanced Eloquent Relationships
- Has Many Through
- Polymorphic Relationships
- Many To Many Polymorphic
- Custom Pivot Models
- Eager Loading and Nested Eager Loading
- Advanced Query Tips & Scopes
- Performance Tips for Eloquent
- Real-World Example
- Conclusion
1. What is Eloquent ORM?
Eloquent is Laravel's built-in ORM (Object Relational Mapper) that simplifies database interactions using expressive, object-oriented syntax. Instead of writing raw SQL, you can perform database operations using Eloquent models.
2. Recap of Basic Relationships
Before jumping into advanced concepts, let’s briefly recap the common relationship types:
- hasOne: A user has one profile.
- hasMany: A post has many comments.
- belongsTo: A comment belongs to a post.
- belongsToMany: A user belongs to many roles.
3. Advanced Eloquent Relationships
a) Has Many Through
Used when you want to access a distant relationship via an intermediate model.
Scenario: A Country has many Posts through Users.
class Country extends Model
{
public function posts()
{
return $this->hasManyThrough(Post::class, User::class);
}
}Explanation:
A Country has many Users. Each User has many Posts. Instead of fetching posts via users, you can access Country::posts() directly.
b) Polymorphic Relationships
Polymorphic relationships allow a model to belong to more than one other model using a single association.
Scenario: Both Post and Video models can have Comments.
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}Post Model:
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}Video Model:
class Video extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}Usage:
$post = Post::find(1);
$comments = $post->comments;
$video = Video::find(1);
$comments = $video->comments;c) Many To Many Polymorphic
This is like polymorphic but with a many-to-many structure.
Scenario: Both Post and Video models can be tagged.
class Tag extends Model
{
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable');
}
}Post & Video:
class Post extends Model
{
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
}d) Custom Pivot Models
For many-to-many relationships where you need to store extra data in the pivot table.
class RoleUser extends Pivot
{
protected $table = 'role_user';
protected $fillable = ['user_id', 'role_id', 'approved_at'];
}Attach pivot model:
User::find(1)->roles()->attach(2, ['approved_at' => now()]);4. Eager Loading & Nested Eager Loading
Avoid the "N+1" problem by eager loading relationships:
$posts = Post::with('comments')->get();Nested Eager Loading:
$posts = Post::with('comments.user')->get();You can also apply constraints:
$posts = Post::with(['comments' => function($query) {
$query->where('is_active', true);
}])->get();5. Advanced Query Tips & Scopes
Global Scope Example:
class Post extends Model
{
protected static function booted()
{
static::addGlobalScope('active', function ($builder) {
$builder->where('is_active', true);
});
}
}Local Scope Example:
public function scopePublished($query)
{
return $query->where('published', true);
}Usage:
Post::published()->get();6. Performance Tips for Eloquent
Use Chunking for Large Data:
Post::chunk(100, function($posts) { // process posts });- Avoid Lazy Loading in Loops
- Use
pluck()instead of fetching full models when only single columns are needed - Use
select()to limit columns - Cache heavy queries using Laravel Cache
7. Real-World Example
Scenario: Multi-tenant system where Company has Projects, and each Project has Tasks. You want to fetch all tasks for a Company directly
class Company extends Model
{
public function tasks()
{
return $this->hasManyThrough(Task::class, Project::class);
}
}Usage:
$company = Company::with('tasks')->find(1);
$tasks = $company->tasks;Bonus: Add a local scope on Task
public function scopePending($query)
{
return $query->where('status', 'pending');
}Usage:
$pendingTasks = $company->tasks()->pending()->get();8. Conclusion
Mastering Eloquent’s advanced features is crucial for building scalable and maintainable Laravel applications. By leveraging polymorphic relationships, hasManyThrough, custom pivots, and eager loading, you reduce query complexity and improve application performance.
Next time you find yourself repeating complex queries or facing the N+1 problem, think about how Eloquent’s advanced relationships can simplify your code.
