Eloquent ORM is incredibly expressive for basic CRUD operations. However, as your application grows, you will encounter complex data structures that require advanced querying techniques to maintain performance and clean code.
Let’s explore some of Eloquent’s most powerful advanced features.
1. Polymorphic Relationships
Imagine you have an application where users can “Comment” on both Post models and Video models. Instead of creating a post_comments table and a video_comments table, you can use a polymorphic relationship to store all comments in a single comments table.
The comments table will have two special columns: commentable_id and commentable_type.
Defining the Models:
class Comment extends Model
{
public function commentable()
{
return $this->morphTo();
}
}
class Post extends Model
{
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
}
Now, you can easily retrieve comments for a post:
$post = Post::find(1);
foreach ($post->comments as $comment) {
echo $comment->body;
}
2. Advanced Subqueries
Subqueries allow you to select additional calculated columns directly within your main database query, drastically reducing the N+1 query problem without needing to load massive amounts of relationship data into memory.
For example, selecting the latest login date for a list of users:
use AppModelsLogin;
use AppModelsUser;
$users = User::addSelect(['last_login_at' => Login::select('created_at')
->whereColumn('user_id', 'users.id')
->orderByDesc('created_at')
->limit(1)
])->get();
This runs entirely in the database layer, returning the last_login_at as a property on your User model, making it incredibly fast.
3. Global and Local Scopes
Scopes allow you to easily re-use query constraints.
Local Scopes
Define common query constraints on your model:
public function scopePopular($query)
{
return $query->where('views', '>', 1000);
}
public function scopeActive($query)
{
return $query->where('is_active', 1);
}
Usage: $popularActivePosts = Post::popular()->active()->get();
Global Scopes
If you want a constraint applied to every query for a model (like filtering out soft-deleted items, or filtering data by a multi-tenant tenant_id), use a Global Scope.
protected static function booted()
{
static::addGlobalScope('tenant', function (Builder $builder) {
$builder->where('tenant_id', auth()->user()->tenant_id);
});
}
Now, User::all() will only ever return users belonging to the current tenant.
Mastering these features ensures your application scales efficiently at the database level.
