logo
podcast Podcast
get help Get Unstuck

A gotcha when using multiple instances of the same global scope

And an easy way to avoid it

Joel Clermont
Joel Clermont
2025-06-04

If you are using a class-based global scope, you are not required to pass a name when registering the scope.

For example, inside a model, this will work:

protected static function booted(): void
{
    parent::booted();

    static::addGlobalScope(new OrderBy('last_name', 'desc'));
}

However, if you are using multiple instances of the same class-based scope, you will run into a problem.

Looking at this code, you would expect it to be ordered by both last_name and first_name, in that order:

protected static function booted(): void
{
    parent::booted();

    static::addGlobalScope(new OrderBy('last_name', 'desc'));
    static::addGlobalScope(new OrderBy('first_name'));
}

But in reality, the model will only be ordered by first_name. Why is this?

If we peek inside Laravel's addGlobalScope function, we can see that it replaces the first class-based scope registration with the second one, because they are both instances of the same class:

// this code is basically the same since Laravel 9 through Laravel 12
public static function addGlobalScope($scope, $implementation = null)
{
    if (is_string($scope) && ($implementation instanceof Closure || $implementation instanceof Scope)) {
        return static::$globalScopes[static::class][$scope] = $implementation;
    } elseif ($scope instanceof Closure) {
        return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope;
    } elseif ($scope instanceof Scope) {
        // This is the offending line. The key is just our scope's class name.
        return static::$globalScopes[static::class][get_class($scope)] = $scope;
    } elseif (is_string($scope) && class_exists($scope) && is_subclass_of($scope, Scope::class)) {
        return static::$globalScopes[static::class][$scope] = new $scope;
    }
}

The solution is easy. Pass in a unique string scope name when registering both instances of the class-based scope. Now the scope registration is keyed by the scope name parameter, and they will both be applied in the order you registered them:

protected static function booted(): void
{
    parent::booted();

    static::addGlobalScope('order-by-last', new OrderBy('last_name', 'desc'));
    static::addGlobalScope('order-by-first', new OrderBy('first_name'));
}

The ability to pass a name with a class-based scope was added in Laravel 9, so you'll need to be on at least that version to use this solution.

Here to help,

Joel

P.S. If you're staring at a problem in your Laravel app and not sure how to solve it, I can help.

Toss a coin in the jar if you found this helpful.
Want a tip like this in your inbox every weekday? Sign up below 👇🏼
email
No spam. Only real-world advice you can use.