0

Scope (Eloquent) — reusable WHERE clause fragments attached to a model

Beginner5 min read·eng-14-012
laravel-src

Concept

Scope (Eloquent) — a reusable query constraint fragment attached to a model. Instead of repeating ->where('active', true) everywhere, define it once and call ->active() on any query.

Two types of scopes:

Local scopes: Named, explicitly called. Defined as scopeMethodName(Builder $query). Called as ->methodName() (without the scope prefix). Chainable.

Global scopes: Applied to EVERY query on the model, automatically. You never have to call them. Soft deletes use a global scope to add WHERE deleted_at IS NULL to every query automatically.

Local scope naming: The method is public function scopeActive(Builder $query). You call it as User::active()->get(). Laravel strips the scope prefix.

Parameterized scopes: Local scopes can accept parameters. scopeOfRole(Builder $query, string $role)User::ofRole('admin')->get().

Global scope removal: User::withoutGlobalScope(ActiveScope::class)->get() — bypasses a specific global scope. withoutGlobalScopes() bypasses ALL global scopes.

Soft deletes global scope: SoftDeletingScope is a global scope that adds WHERE deleted_at IS NULL. User::withTrashed() removes it. User::onlyTrashed() inverts it.

Code Example

php
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;

class User extends Model
{
    use \Illuminate\Database\Eloquent\SoftDeletes; // adds global scope automatically

    // LOCAL SCOPE — explicitly called, reusable WHERE fragment
    public function scopeActive(Builder $query): void
    {
        $query->where('active', true);
    }

    public function scopeVerified(Builder $query): void
    {
        $query->whereNotNull('email_verified_at');
    }

    // PARAMETERIZED local scope
    public function scopeOfRole(Builder $query, string $role): void
    {
        $query->where('role', $role);
    }

    public function scopeCreatedAfter(Builder $query, \Carbon\Carbon $date): void
    {
        $query->where('created_at', '>=', $date);
    }
}

// Usage — readable, composable
$activeAdmins = User::active()                          // scopeActive
                    ->ofRole('admin')                    // scopeOfRole('admin')
                    ->verified()                         // scopeVerified
                    ->createdAfter(now()->subDays(30))   // scopeCreatedAfter
                    ->get();
// Generated SQL:
// WHERE active = 1
//   AND role = 'admin'
//   AND email_verified_at IS NOT NULL
//   AND created_at >= '2025-10-01'
//   AND deleted_at IS NULL  ← added by SoftDeletingScope global scope

// GLOBAL SCOPE — applied to ALL queries automatically
class PublishedScope implements \Illuminate\Database\Eloquent\Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where('published', true);
    }
}

class Article extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new PublishedScope());
        // OR inline:
        static::addGlobalScope('published', fn(Builder $b) => $b->where('published', true));
    }
}

Article::all();      // WHERE published = true — always! (global scope)
Article::find(1);    // WHERE id = 1 AND published = true

// Bypassing global scope
Article::withoutGlobalScope('published')->get();    // all articles, published or not
Article::withoutGlobalScope(PublishedScope::class)->get(); // same using class

// ELOQUENT'S BUILT-IN GLOBAL SCOPE: SoftDeletes
$users = User::all();                   // WHERE deleted_at IS NULL (hidden deleted users)
$users = User::withTrashed()->all();    // includes deleted users
$users = User::onlyTrashed()->all();    // ONLY deleted users
$user  = User::find(1);                // automatically: AND deleted_at IS NULL