Laravel 12 Global Scope: Automate Query Filters Across Your Models

Suresh RamaniSuresh Ramani
15 min read

Global scopes in Laravel 12 are game-changers for developers who want to automatically filter database queries across their entire application. Whether you're building a multi-tenant SaaS platform, need to hide inactive records by default, or want to implement role-based data access, global scopes provide an elegant solution that keeps your code clean and maintainable.

In this comprehensive guide, we'll explore how Laravel 12's enhanced global scope functionality can transform your Eloquent models from basic data containers into intelligent, self-filtering powerhouses that automatically apply your business logic to every query.

Why Global Scopes Matter in Large-Scale Applications

Large-scale applications often require consistent data filtering across multiple controllers, services, and API endpoints. Without global scopes, you'd find yourself writing the same where clauses repeatedly:

// Without global scopes - repetitive and error-prone
$activeUsers = User::where('is_active', true)->get();
$activePosts = Post::where('is_published', true)->get();
$visibleComments = Comment::where('is_approved', true)->get();

Global scopes eliminate this repetition by automatically applying filters at the model level, ensuring consistency and reducing the chance of security vulnerabilities caused by forgotten filters.

How Laravel 12 Enhances Query Automation with Global Scope

Laravel 12 continues the improvements made in Laravel 11.x and includes features such as improved application structure and query builder optimizations. The framework's global scope implementation has been refined to offer better performance and more intuitive syntax for complex filtering scenarios.

Key enhancements in Laravel 12 include:

  • Improved Performance: Optimized query compilation for global scopes

  • Better Integration: Enhanced compatibility with eager loading and relationships

  • Simplified Syntax: More intuitive methods for scope management

  • Enhanced Debugging: Better error messages and query inspection tools

Understanding Eloquent Global Scopes

What Is a Global Scope in Laravel Eloquent?

A global scope is a constraint that gets automatically applied to all queries for a specific Eloquent model. Think of it as a "default filter" that runs behind the scenes every time you query your model.

// With a global scope applied to User model
$users = User::all(); // Automatically filters only active users
$specificUser = User::find(1); // Still applies the active filter

Global scopes work by implementing the Illuminate\Database\Eloquent\Scope interface and modifying the query builder before the query executes.

Global Scope vs Local Scope: Key Differences and Use Cases

Feature

Global Scope

Local Scope

Automatic Application

Always applied

Called manually

Use Case

Business rules, security filters

Specific query modifications

Removal

Requires explicit withoutGlobalScope()

Not applied by default

Performance Impact

Minimal overhead on all queries

Only when called

Global Scope Example:

// Always filters soft-deleted records
class User extends Model
{
    protected static function booted()
    {
        static::addGlobalScope('active', function (Builder $builder) {
            $builder->where('deleted_at', null);
        });
    }
}

Local Scope Example:

// Called only when needed
public function scopeActive($query)
{
    return $query->where('is_active', true);
}

// Usage: User::active()->get()

When to Use Global Scopes

Use Cases Where Global Scopes Simplify Code

Global scopes excel in these scenarios:

  1. Multi-Tenancy: Automatically filter data by tenant ID

  2. Soft Deletes: Hide deleted records without explicit filtering

  3. Access Control: Restrict data based on user permissions

  4. Status Filtering: Show only published/active content by default

  5. Regional Data: Filter content by geographic location

Scenarios Where Global Scopes Might Not Be Ideal

Avoid global scopes when:

  • Administrative Interfaces: Admins need to see all data including inactive records

  • Reporting: Analytics require complete datasets

  • Data Migration: Scripts need access to all records

  • Debugging: Troubleshooting requires full visibility

Creating Your First Global Scope

Defining a Global Scope Class Using Scope Interface

The most maintainable approach is creating a dedicated scope class:

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class ActiveScope implements Scope
{
    /**
     * Apply the scope to a given Eloquent query builder.
     */
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where($model->getQualifiedColumn('is_active'), true);
    }

    /**
     * Extend the query builder with additional methods.
     */
    public function extend(Builder $builder): void
    {
        $builder->macro('withInactive', function (Builder $builder) {
            return $builder->withoutGlobalScope($this);
        });
    }
}

Registering the Scope in Your Eloquent Model

Apply the scope to your model using the booted method:

<?php

namespace App\Models;

use App\Scopes\ActiveScope;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope);
    }
}

Using Closures vs Scope Classes

When to Use Closures for Simple Filtering Logic

For simple, model-specific filters, closures offer a concise solution:

class Post extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope('published', function (Builder $builder) {
            $builder->where('status', 'published')
                   ->where('published_at', '<=', now());
        });
    }
}

Benefits of Scope Classes for Complex Reusability

Scope classes provide advantages for complex logic:

  • Reusability: Share across multiple models

  • Testability: Easier to unit test in isolation

  • Maintainability: Better organization and documentation

  • Extensibility: Add macro methods for enhanced functionality

// Reusable across User, Post, Comment models
class TenantScope implements Scope
{
    public function __construct(private int $tenantId) {}

    public function apply(Builder $builder, Model $model): void
    {
        if (auth()->check()) {
            $builder->where($model->getQualifiedColumn('tenant_id'), $this->tenantId);
        }
    }
}

Applying Global Scopes in Real-World Models

Automatically Filtering Active Records

Here's a comprehensive example for filtering active records:

<?php

namespace App\Models;

use App\Scopes\ActiveScope;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Product extends Model
{
    use SoftDeletes;

    protected $fillable = [
        'name', 'description', 'price', 'is_active', 'stock_quantity'
    ];

    protected $casts = [
        'is_active' => 'boolean',
        'price' => 'decimal:2',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope);

        // Additional scope for in-stock items
        static::addGlobalScope('in_stock', function (Builder $builder) {
            $builder->where('stock_quantity', '>', 0);
        });
    }

    // Relationships
    public function category()
    {
        return $this->belongsTo(Category::class);
    }
}

Implementing a Tenant-Based Multi-Tenancy Scope

Multi-tenancy is a perfect use case for global scopes:

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class TenantScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        if (auth()->check() && auth()->user()->tenant_id) {
            $builder->where(
                $model->getQualifiedColumn('tenant_id'), 
                auth()->user()->tenant_id
            );
        }
    }

    public function extend(Builder $builder): void
    {
        $builder->macro('allTenants', function (Builder $builder) {
            return $builder->withoutGlobalScope($this);
        });

        $builder->macro('forTenant', function (Builder $builder, $tenantId) {
            return $builder->withoutGlobalScope($this)
                          ->where('tenant_id', $tenantId);
        });
    }
}

Apply to models that need tenant filtering:

class Invoice extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new TenantScope);
    }

    // Now all Invoice queries automatically filter by current user's tenant
}

Excluding Soft Deleted Records in a Custom Way

While Laravel includes SoftDeletes trait, you might need custom deletion logic:

namespace App\Scopes;

class CustomSoftDeleteScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->whereNull($model->getQualifiedColumn('archived_at'))
               ->whereNull($model->getQualifiedColumn('deleted_at'));
    }

    public function extend(Builder $builder): void
    {
        $builder->macro('withArchived', function (Builder $builder) {
            return $builder->withoutGlobalScope($this);
        });

        $builder->macro('onlyArchived', function (Builder $builder) {
            return $builder->withoutGlobalScope($this)
                          ->whereNotNull('archived_at');
        });
    }
}

Chaining Global Scopes with Other Queries

Combining Global Scopes with Local Conditions

Global scopes seamlessly integrate with additional query conditions:

// Global scope applies first, then additional conditions
$premiumActiveUsers = User::where('subscription_type', 'premium')
                         ->where('last_login', '>', now()->subDays(30))
                         ->orderBy('created_at', 'desc')
                         ->limit(100)
                         ->get();

The query execution order:

  1. Apply global scopes (e.g., is_active = true)

  2. Apply additional where clauses

  3. Apply ordering and limits

Overriding Scopes in Custom Queries

Sometimes you need to modify or disable scopes for specific queries:

// Remove specific scope
$allUsers = User::withoutGlobalScope('active')->get();

// Remove multiple scopes
$rawData = User::withoutGlobalScopes(['active', 'tenant'])->get();

// Remove all global scopes
$completeDataset = User::withoutGlobalScopes()->get();

Removing or Disabling Global Scopes

How to Temporarily Remove Global Scopes

Laravel provides several methods to bypass global scopes:

// Method 1: Remove by scope name
User::withoutGlobalScope('active')->get();

// Method 2: Remove by scope class
User::withoutGlobalScope(ActiveScope::class)->get();

// Method 3: Remove multiple scopes
User::withoutGlobalScopes([
    'active',
    TenantScope::class
])->get();

// Method 4: Remove all scopes
User::withoutGlobalScopes()->get();

Disabling Scopes for Admin or Superuser Access

Create a middleware or service for admin access:

<?php

namespace App\Services;

class AdminQueryService
{
    public function getAllUsers()
    {
        if (auth()->user()->isAdmin()) {
            return User::withoutGlobalScopes()->get();
        }

        return User::all(); // Normal scoped query
    }

    public function getInactiveUsers()
    {
        $this->authorizeAdmin();

        return User::withoutGlobalScope('active')
                  ->where('is_active', false)
                  ->get();
    }

    private function authorizeAdmin()
    {
        if (!auth()->user()->isAdmin()) {
            throw new AuthorizationException('Admin access required');
        }
    }
}

Global Scopes and Eager Loading

Ensuring Compatibility with Eager Loaded Relationships

Global scopes automatically apply to eager-loaded relationships:

class User extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope);
    }

    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

class Post extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope('published', function (Builder $builder) {
            $builder->where('status', 'published');
        });
    }
}

// Both User and Post scopes apply automatically
$activeUsersWithPublishedPosts = User::with('posts')->get();

Avoiding Scope Conflicts in Nested Queries

Be careful with scopes that might conflict in relationship queries:

// Potential issue: both models have 'active' scope
class Category extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope('active', function (Builder $builder) {
            $builder->where('is_visible', true);
        });
    }

    public function products()
    {
        return $this->hasMany(Product::class);
    }
}

// Solution: Use more specific scope names
class Category extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope('visible_categories', function (Builder $builder) {
            $builder->where('is_visible', true);
        });
    }
}

Using Global Scopes with Relationships

Global scopes work seamlessly with Eloquent relationships:

class User extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope);
    }

    public function orders()
    {
        // Orders model also has global scopes that will apply
        return $this->hasMany(Order::class);
    }

    public function completedOrders()
    {
        return $this->hasMany(Order::class)
                   ->where('status', 'completed');
    }
}

class Order extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope('not_cancelled', function (Builder $builder) {
            $builder->where('status', '!=', 'cancelled');
        });
    }
}

// Gets active users with their non-cancelled orders
$usersWithOrders = User::with('orders')->get();

Applying Scopes to Pivot Tables and Intermediate Models

For many-to-many relationships with pivot models:

class User extends Model
{
    public function roles()
    {
        return $this->belongsToMany(Role::class)
                   ->using(UserRole::class)
                   ->wherePivot('is_active', true);
    }
}

class UserRole extends Pivot
{
    protected static function booted(): void
    {
        static::addGlobalScope('active_assignments', function (Builder $builder) {
            $builder->where('is_active', true)
                   ->where('expires_at', '>', now());
        });
    }
}

Testing Models with Global Scopes

Writing Unit Tests for Scoped Queries

Testing global scopes requires careful setup to ensure predictable results:

<?php

namespace Tests\Unit;

use App\Models\User;
use App\Scopes\ActiveScope;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;

class UserGlobalScopeTest extends TestCase
{
    use RefreshDatabase;

    public function test_active_scope_filters_inactive_users()
    {
        // Arrange: Create active and inactive users
        User::factory()->create(['is_active' => true, 'name' => 'Active User']);
        User::factory()->create(['is_active' => false, 'name' => 'Inactive User']);

        // Act: Query users (global scope should apply)
        $users = User::all();

        // Assert: Only active users returned
        $this->assertCount(1, $users);
        $this->assertEquals('Active User', $users->first()->name);
    }

    public function test_can_retrieve_inactive_users_without_scope()
    {
        // Arrange
        User::factory()->create(['is_active' => true]);
        User::factory()->create(['is_active' => false]);

        // Act: Query without global scope
        $allUsers = User::withoutGlobalScope(ActiveScope::class)->get();

        // Assert: Both users returned
        $this->assertCount(2, $allUsers);
    }

    public function test_scope_applies_to_relationships()
    {
        // Arrange: Create user with active and inactive posts
        $user = User::factory()->create(['is_active' => true]);
        $activePosts = Post::factory(2)->create([
            'user_id' => $user->id,
            'status' => 'published'
        ]);
        $inactivePosts = Post::factory(1)->create([
            'user_id' => $user->id,
            'status' => 'draft'
        ]);

        // Act: Load user with posts
        $userWithPosts = User::with('posts')->first();

        // Assert: Only published posts loaded
        $this->assertCount(2, $userWithPosts->posts);
    }
}

Mocking Data with and Without Scopes Applied

Create test factories that work with scoped models:

<?php

namespace Database\Factories;

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    protected $model = User::class;

    public function definition(): array
    {
        return [
            'name' => $this->faker->name,
            'email' => $this->faker->unique()->safeEmail,
            'is_active' => true, // Default to active for most tests
            'tenant_id' => 1,
        ];
    }

    public function inactive(): static
    {
        return $this->state(fn (array $attributes) => [
            'is_active' => false,
        ]);
    }

    public function forTenant(int $tenantId): static
    {
        return $this->state(fn (array $attributes) => [
            'tenant_id' => $tenantId,
        ]);
    }
}

Performance Considerations

Do Global Scopes Affect Query Speed?

Global scopes have minimal performance impact when properly implemented:

Positive Impacts:

  • Reduce dataset size by filtering early

  • Can leverage database indexes effectively

  • Prevent unnecessary data transfer

Potential Concerns:

  • Additional WHERE clauses on every query

  • Complex scopes with subqueries may slow queries

  • Multiple scopes compound complexity

Optimizing Complex Scopes for High-Volume Applications

Best practices for performance:

// ✅ Good: Simple, indexable conditions
class ActiveScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where($model->getQualifiedColumn('is_active'), true);
    }
}

// ❌ Avoid: Complex subqueries in global scopes
class ProblematicScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->whereExists(function ($query) {
            $query->select(DB::raw(1))
                  ->from('complex_calculations')
                  ->whereRaw('expensive_function() > 100');
        });
    }
}

// ✅ Better: Move complex logic to local scopes or services
public function scopeWithComplexCalculation($query)
{
    return $query->whereExists(function ($query) {
        // Complex logic here, called only when needed
    });
}

Database Index Recommendations:

-- Index columns used in global scopes
CREATE INDEX idx_users_active ON users (is_active);
CREATE INDEX idx_posts_published ON posts (status, published_at);
CREATE INDEX idx_tenant_data ON orders (tenant_id, created_at);

-- Composite indexes for multiple scope conditions
CREATE INDEX idx_products_active_stock ON products (is_active, stock_quantity);

Best Practices for Organizing Global Scopes

Where to Store Scope Classes in Your Project

Organize scope classes in a dedicated directory:

app/
├── Scopes/
│   ├── ActiveScope.php
│   ├── TenantScope.php
│   ├── PublishedScope.php
│   └── Security/
│       ├── UserAccessScope.php
│       └── AdminOnlyScope.php
├── Models/
│   ├── User.php
│   └── Post.php

Naming Conventions and Reusability Guidelines

Follow consistent naming patterns:

// ✅ Good naming: Descriptive and specific
class ActiveRecordsScope implements Scope { }
class PublishedContentScope implements Scope { }
class TenantFilterScope implements Scope { }

// ❌ Poor naming: Vague and generic
class DataScope implements Scope { }
class FilterScope implements Scope { }
class MyScope implements Scope { }

Reusability Guidelines:

  1. Single Responsibility: Each scope should handle one filtering concern

  2. Configuration: Use constructor parameters for flexible scopes

  3. Documentation: Clear PHPDoc comments explaining scope purpose

  4. Testing: Unit tests for each scope class

/**
 * Filters records based on publication status and date.
 * 
 * This scope automatically excludes:
 * - Unpublished content (status != 'published')
 * - Future-dated content (published_at > now())
 * - Expired content if expiry date is set
 */
class PublishedContentScope implements Scope
{
    public function __construct(
        private bool $includeFuture = false,
        private bool $includeExpired = false
    ) {}

    public function apply(Builder $builder, Model $model): void
    {
        $builder->where($model->getQualifiedColumn('status'), 'published');

        if (!$this->includeFuture) {
            $builder->where($model->getQualifiedColumn('published_at'), '<=', now());
        }

        if (!$this->includeExpired) {
            $builder->where(function ($query) use ($model) {
                $query->whereNull($model->getQualifiedColumn('expires_at'))
                      ->orWhere($model->getQualifiedColumn('expires_at'), '>', now());
            });
        }
    }
}

Advanced Use Cases of Global Scope

Creating Dynamic Global Scopes Based on Authenticated User

Dynamic scopes that adapt based on the current user context:

<?php

namespace App\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class UserContextScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $user = auth()->user();

        if (!$user) {
            // Public access: only show public content
            $builder->where($model->getQualifiedColumn('visibility'), 'public');
            return;
        }

        if ($user->hasRole('admin')) {
            // Admins see everything - no additional filters
            return;
        }

        if ($user->hasRole('moderator')) {
            // Moderators see public and internal content
            $builder->whereIn($model->getQualifiedColumn('visibility'), ['public', 'internal']);
            return;
        }

        // Regular users see public and their own content
        $builder->where(function ($query) use ($model, $user) {
            $query->where($model->getQualifiedColumn('visibility'), 'public')
                  ->orWhere($model->getQualifiedColumn('user_id'), $user->id);
        });
    }
}

Global Scopes for Soft-Deletes, Archiving, and Access Control

Comprehensive data lifecycle management:

class DataLifecycleScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->whereNull($model->getQualifiedColumn('deleted_at'))
               ->whereNull($model->getQualifiedColumn('archived_at'))
               ->where(function ($query) use ($model) {
                   $query->whereNull($model->getQualifiedColumn('embargo_until'))
                         ->orWhere($model->getQualifiedColumn('embargo_until'), '<=', now());
               });
    }

    public function extend(Builder $builder): void
    {
        $builder->macro('withArchived', function (Builder $builder) {
            return $builder->withoutGlobalScope($this)
                          ->whereNull('deleted_at');
        });

        $builder->macro('onlyArchived', function (Builder $builder) {
            return $builder->withoutGlobalScope($this)
                          ->whereNull('deleted_at')
                          ->whereNotNull('archived_at');
        });

        $builder->macro('withEmbargoed', function (Builder $builder) {
            return $builder->withoutGlobalScope($this)
                          ->whereNull('deleted_at')
                          ->whereNull('archived_at');
        });
    }
}

Common Pitfalls and How to Avoid Them

Overusing Scopes and Making Debugging Difficult

Problem: Too many global scopes make it hard to understand what data is being retrieved:

// ❌ Problematic: Too many overlapping scopes
class User extends Model
{
    protected static function booted(): void
    {
        static::addGlobalScope(new ActiveScope);
        static::addGlobalScope(new VerifiedScope);
        static::addGlobalScope(new SubscribedScope);
        static::addGlobalScope(new RegionScope);
        static::addGlobalScope(new PermissionScope);
        // ... more scopes
    }
}

Solution: Consolidate related logic and provide debugging tools:

// ✅ Better: Combined scope with clear intent
class UserAccessScope implements Scope
{
    public function apply(Builder $builder, Model $model): void
    {
        $builder->where($model->getQualifiedColumn('is_active'), true)
               ->whereNotNull($model->getQualifiedColumn('email_verified_at'))
               ->where($model->getQualifiedColumn('subscription_status'), 'active');

        // Add query comment for debugging
        $builder->addBinding("/* UserAccessScope applied */", 'select');
    }

    public function extend(Builder $builder): void
    {
        $builder->macro('debugScope', function (Builder $builder) {
            $sql = $builder->toSql();
            $bindings = $builder->getBindings();

            logger()->debug('UserAccessScope Query', [
                'sql' => $sql,
                'bindings' => $bindings
            ]);

            return $builder;
        });
    }
}

Conflicts Between Global Scopes and Business Logic

Problem: Global scopes interfering with specific business requirements:

// Business logic that conflicts with global scope
public function processAllUserPayments()
{
    // Intended to process ALL users, but ActiveScope filters out inactive ones
    $users = User::all(); // Only gets active users!

    foreach ($users as $user) {
        $this->processPayment($user);
    }
}

Solution: Be explicit about scope requirements:

// ✅ Clear intent and proper scope handling
public function processAllUserPayments()
{
    // Explicitly include inactive users for payment processing
    $users = User::withoutGlobalScope(ActiveScope::class)
                ->where('has_pending_payments', true)
                ->get();

    foreach ($users as $user) {
        $this->processPayment($user);
    }
}

public function processActiveUserPayments()
{
    // Let global scope work naturally
    $users = User::where('has_pending_payments', true)->get();

    foreach ($users as $user) {
        $this->processPayment($user);
    }
}

Additional Debugging Techniques:

// Add a service for scope debugging
class ScopeDebugger
{
    public static function dumpQuery(Builder $builder): void
    {
        dump([
            'sql' => $builder->toSql(),
            'bindings' => $builder->getBindings(),
            'scopes' => $builder->getQuery()->wheres,
        ]);
    }

    public static function explainScopes(string $model): array
    {
        $modelInstance = new $model;
        $scopes = $modelInstance->getGlobalScopes();

        return array_map(function ($scope) {
            return [
                'class' => get_class($scope),
                'description' => $scope->getDescription() ?? 'No description available'
            ];
        }, $scopes);
    }
}

// Usage in debugging
ScopeDebugger::dumpQuery(User::query());
ScopeDebugger::explainScopes(User::class);

Conclusion

Recap of the Power and Simplicity of Laravel 12 Global Scopes

Laravel 12's global scopes represent a perfect balance between power and simplicity. They transform your Eloquent models from simple data containers into intelligent, self-filtering entities that automatically enforce your business rules and security requirements.

Key benefits we've explored:

  • Automatic Consistency: No more forgetting to apply essential filters

  • Clean Code: Eliminate repetitive query conditions across your application

  • Security: Prevent accidental data exposure through forgotten filters

  • Maintainability: Centralize filtering logic in reusable scope classes

  • Performance: Reduce dataset sizes and leverage database indexes effectively

The enhancements in Laravel 12 make global scopes more performant and developer-friendly than ever before, with improved debugging capabilities and better integration with the framework's other features.

What's Next: Combining Global Scopes with Policies and Events

Global scopes work exceptionally well when combined with other Laravel features:

Integration with Authorization Policies:

// Policy automatically works with scoped data
class PostPolicy
{
    public function view(User $user, Post $post)
    {
        // Post already filtered by global scope
        return $user->canViewPost($post);
    }
}

Event-Driven Scope Management:

// Dynamically adjust scopes based on events
Event::listen(TenantSwitched::class, function ($event) {
    // Update tenant-based scopes dynamically
    app()->instance('current_tenant', $event->tenant);
});

API Resource Integration:

// API resources automatically benefit from scoped data
class UserResource extends JsonResource
{
    public function toArray($request)
    {
        // Data already filtered by global scopes
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'posts' => PostResource::collection($this->whenLoaded('posts')),
            // Global scopes ensure only appropriate data is exposed
        ];
    }
}

Queue Job Processing:

class ProcessUserDataJob implements ShouldQueue
{
    public function handle()
    {
        // Be explicit about scope requirements in jobs
        $users = User::withoutGlobalScopes()
                    ->where('needs_processing', true)
                    ->chunk(100, function ($users) {
                        foreach ($users as $user) {
                            $this->processUser($user);
                        }
                    });
    }
}

As you continue building applications with Laravel 12, consider global scopes as one of your primary tools for creating clean, secure, and maintainable codebases. They're particularly powerful when combined with Laravel's other features like policies, events, and API resources to create comprehensive, business-rule-aware applications.

Remember: the goal isn't to use global scopes everywhere, but to use them strategically where they provide the most value—automating repetitive filters, enforcing security requirements, and maintaining data consistency across your entire application.

Start small with a simple ActiveScope or TenantScope, then gradually expand your scope usage as you identify patterns and opportunities for automation in your Laravel 12 applications.

0
Subscribe to my newsletter

Read articles from Suresh Ramani directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Suresh Ramani
Suresh Ramani