<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
// Local scope
public function scopePublished(Builder $query): void
{
$query->whereNotNull('published_at')
->where('published_at', '<=', now());
}
public function scopeFeatured(Builder $query): void
{
$query->where('is_featured', true);
}
public function scopeByAuthor(Builder $query, int $authorId): void
{
$query->where('user_id', $authorId);
}
public function scopePopular(Builder $query, int $minViews = 1000): void
{
$query->where('views', '>=', $minViews)
->orderBy('views', 'desc');
}
public function scopeRecent(Builder $query, int $days = 7): void
{
$query->where('created_at', '>=', now()->subDays($days));
}
public function scopeSearch(Builder $query, ?string $term): void
{
if (!$term) {
return;
}
$query->where(function ($q) use ($term) {
$q->where('title', 'like', "%{$term}%")
->orWhere('body', 'like', "%{$term}%");
});
}
// Global scope - applies to all queries
protected static function booted(): void
{
// Only show posts from active users
static::addGlobalScope('activeAuthor', function (Builder $query) {
$query->whereHas('author', fn ($q) => $q->where('status', 'active'));
});
}
}
<?php
// Chain multiple scopes
$posts = Post::published()
->featured()
->recent()
->get();
// Scopes with parameters
$popularPosts = Post::popular(5000)
->byAuthor($userId)
->get();
// Combine scopes with query builder
$posts = Post::published()
->where('category_id', $categoryId)
->with('author')
->latest()
->paginate(20);
// Conditional scope
$query = Post::query();
$query->when($request->search, function ($q, $search) {
$q->search($search);
});
$query->when($request->featured, function ($q) {
$q->featured();
});
$posts = $query->published()->get();
// Remove global scope
$allPosts = Post::withoutGlobalScope('activeAuthor')->get();
Query scopes encapsulate common query constraints into reusable methods on models. Local scopes prefix methods with scope and accept a query builder plus additional parameters. I chain scopes fluently—Post::published()->featured()->get(). Global scopes apply to all queries automatically, useful for soft deletes or multi-tenancy. Anonymous global scopes use closures in the booted() method. Scopes keep controllers clean by moving query logic to models where it belongs. Dynamic scopes accept parameters for flexible filtering. The combination of local and global scopes creates a powerful query API specific to each model. This pattern is fundamental to readable, maintainable Eloquent queries.