Skip to content

[5.4] Cannot remove a global scope using another global scope #19282

Closed
@KKSzymanowski

Description

@KKSzymanowski
  • Laravel Version: 5.4.23
  • PHP Version: 7.1
  • Database Driver & Version: MariaDB 10.1.21

Description:

Let's say we have products which can be soft-deleted.
Normal users can manipulate only existing products, but users with elevated privileges can manipulate also deleted ones. Adding a global scope which removes SoftDeletingScope under some condition does not work - the SoftDeletingScope is removed but is also applied right after.

Steps To Reproduce:

Product:

class Product extends Model 
{

    use SoftDeletes;

    protected static function boot()
    {
        static::addGlobalScope(function(Builder $query) {
            if(true/* some condition here */) {
                $query->withoutGlobalScope(SoftDeletingScope::class);
            }
        });

        parent::boot();
    }
}

In eg. a controller:

\DB::listen(function($query) {
    dump($query->sql);
});

Product::get();

You'll see deleted_at is null

Possible fix:

Before applying a scope, check if it hasn't been removed from the list.
Illuminate\Database\Eloquent\Builder::applyScopes

public function applyScopes()
{
    if (! $this->scopes) {
        return $this;
    }

    $builder = clone $this;

    foreach ($builder->scopes as $identifier => $scope) {
+       if(!isset($builder->scopes[$identifier])) {
+           continue;
+       }

        $builder->callScope(function (Builder $builder) use ($scope) {
            // If the scope is a Closure we will just go ahead and call the scope with the
            // builder instance. The "callScope" method will properly group the clauses
            // that are added to this query so "where" clauses maintain proper logic.
            if ($scope instanceof Closure) {
                $scope($builder);
            }

            // If the scope is a scope object, we will call the apply method on this scope
            // passing in the builder and the model instance. After we run all of these
            // scopes we will return back the builder instance to the outside caller.
            if ($scope instanceof Scope) {
                $scope->apply($builder, $this->getModel());
            }
        });
    }

    return $builder;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions