Writing Elegant Queries with Scopes
Query scopes encapsulate common query constraints into reusable, chainable methods on your model.
Local Scopes
class Post extends Model
{
public function scopePublished(Builder $query): Builder
{
return $query->where('is_published', true)
->whereNotNull('published_at')
->where('published_at', '<=', now());
}
public function scopeFeatured(Builder $query): Builder
{
return $query->where('is_featured', true);
}
public function scopeByCategory(Builder $query, int $categoryId): Builder
{
return $query->where('category_id', $categoryId);
}
public function scopeRecent(Builder $query, int $days = 30): Builder
{
return $query->where('published_at', '>=', now()->subDays($days));
}
}
Chaining Scopes
// Clean, readable queries
$posts = Post::published()
->featured()
->recent(7)
->with(['author', 'tags'])
->latest('published_at')
->paginate(12);
$techPosts = Post::published()
->byCategory($techCategory->id)
->withCount('comments')
->get();
Advanced Query Techniques
Subqueries:
$users = User::addSelect([
'latest_post_title' => Post::select('title')
->whereColumn('user_id', 'users.id')
->published()
->latest()
->limit(1),
])->get();
Conditional Queries:
$posts = Post::published()
->when($request->category, fn ($q, $cat) =>
$q->where('category_id', $cat)
)
->when($request->search, fn ($q, $search) =>
$q->where('title', 'like', "%{$search}%")
)
->when($request->sort === 'popular', fn ($q) =>
$q->orderByDesc('views_count'),
fn ($q) => $q->latest('published_at')
)
->paginate(15);
Chunk Processing for Large Datasets:
Post::where('is_published', true)->chunk(200, function ($posts) {
foreach ($posts as $post) {
SearchIndex::update($post);
}
});
Query scopes transform messy where chains into a domain-specific language that reads like English.