Laravel: how to "disable" the global scope to include "inactive" objects in a request?

I am having problems with global scopes, especially deleting a scope.

In my user model, I have an ActivatedUsersTrait that enters a global scope for a user-only query when the "activated" column is set to true ("User activated" after email validation).

So far everything is working fine, when I ask User :: all () I only get users with activated = true.

Now my problem is how to include inactive users in my request like SoftDeletingTrait via withTrashed ()? This is only relevant in my ActivationController where I need to get the user, set activate = true and save them back to the db.

I created an inInactive () method in my ActiveUsersTrait based on the method I found in SoftDeletingTrait, but when I run the query in User :: withInactive-> get (), inactive users won't show up in the results.

Here's my ActiveUsersTrait:

use PB\Scopes\ActiveUsersScope;

trait ActiveUsersTrait {

    public static function bootActiveUsersTrait()
    {
        static::addGlobalScope(new ActiveUsersScope);
    }

    public static function withInactive()
    {
        // dd(new static);
        return (new static)->newQueryWithoutScope(new ActiveUsersScope);
    }

    public function getActivatedColumn()
    {
        return 'activated';
    }

    public function getQualifiedActivatedColumn()
    {
        return $this->getTable().'.'.$this->getActivatedColumn();
    }

}

      

and my ActiveUsersScope:

use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;

class ActiveUsersScope implements ScopeInterface {

    public function apply(Builder $builder)
    {
        $model = $builder->getModel();

        $builder->where($model->getQualifiedActivatedColumn(), true);

    }

    public function remove(Builder $builder)
    {
        $column = $builder->getModel()->getQualifiedActivatedColumn();

        $query = $builder->getQuery();

        foreach ((array) $query->wheres as $key => $where)
        {
            if ($this->isActiveUsersConstraint($where, $column))
            {
                unset($query->wheres[$key]);

                $query->wheres = array_values($query->wheres);
            }
        }
    }

    protected function isActiveUsersConstraint(array $where, $column)
    {
        return $where['type'] == 'Basic' && $where['column'] == $column;
    }
}

      

Any help is greatly appreciated!

Thanks in advance! Joseph

+3


source to share


5 answers


SoftDeletingTrait where cleanup is easier because it contains no bindings (this is "Null" where not "Basic" where). The problem you are having is that the binding for [n => true] still exists even when you manually delete where.

I am thinking about creating a PR because I ran into the same problem myself and there is no great way to keep track of which elements and which bindings go together.

If you are using a simple query, you can track the anchor index more or less like this:

use Illuminate\Database\Eloquent\ScopeInterface;
use Illuminate\Database\Eloquent\Builder;

class ActiveUsersScope implements ScopeInterface {

    /**
     * The index in which we added a where clause
     * @var int
     */
    private $where_index;

    /**
     * The index in which we added a where binding
     * @var int
     */
    private $binding_index;

    /**
     * Apply the scope to a given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @return void
     */
    public function apply(Builder $builder)
    {
        $model = $builder->getModel();

        $builder->where($model->getQualifiedActivatedColumn(), true);

        $this->where_index = count($query->wheres) - 1;

        $this->binding_index = count($query->getRawBindings()['where']) - 1;
    }

    /**
     * Remove the scope from the given Eloquent query builder.
     *
     * @param  \Illuminate\Database\Eloquent\Builder  $builder
     * @return void
     */
    public function remove(Builder $builder)
    {
        $query = $builder->getQuery();

        unset($query->wheres[$this->where_index]);
        $where_bindings = $query->getRawBindings()['where'];
        unset($where_bindings[$this->binding_index]);
        $query->setBindings(array_values($where_bindings));
        $query->wheres = array_values($query->wheres);
    }
}

      



Note how we are storing the indexes where clauses and where bindings were added, rather than looping and validating what was found. This almost looks like better design - we've added a where clause and a binding, so we need to know where it is without having to loop through all the where clauses. Of course, things will go well if something else (like ::withTrashed

) also fiddles with the where array. Unfortunately, where bindings and where clauses are just flat arrays, so we cannot listen to their changes exactly. A more object oriented approach with better automatic dependency management between clauses and clauses would be preferable.

Obviously this approach can benefit from some prettier code and checking if array keys exist, etc. But that should get you started. Since global scopes are not single (they are applied when called newQuery()

), this approach should be valid without additional validation.

Hope this helps under the heading "good enough for now"!

+11


source


Now every request has a method removeGlobalScopes()

.

See: https://laravel.com/docs/5.3/eloquent#query-scopes (under the heading "Deleting Global Scopes").



From the docs:

// Remove one scope
User::withoutGlobalScope(AgeScope::class)->get();

// Remove all of the global scopes...
User::withoutGlobalScopes()->get();

// Remove some of the global scopes...
User::withoutGlobalScopes([
    FirstScope::class, SecondScope::class
])->get();

      

+6


source


Just found this after one problem and I have a more eloquent solution. Just replace your "remove" method with this.

/**
 * Remove the scope from the given Eloquent query builder.
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $builder
 * @return void
 */
public function remove(Builder $builder)
{
    $query = $builder->getQuery();

    $column = $builder->getModel()->getQualifiedActivatedColumn();

    foreach ((array) $query->wheres as $key => $where)
    {
        if ($this->isActiveUsersConstraint($where, $column))
        {
            // Here SoftDeletingScope simply removes the where
            // but since we use Basic where (not Null type)
            // we need to get rid of the binding as well
            $this->removeWhere($query, $key);

            $this->removeBinding($query, $key);
        }
    }
}

/**
 * Remove scope constraint from the query.
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $builder
 * @param  int  $key
 * @return void
 */
protected function removeWhere($query, $key)
{
    unset($query->wheres[$key]);

    $query->wheres = array_values($query->wheres);
}

/**
 * Remove scope constraint from the query.
 *
 * @param  \Illuminate\Database\Eloquent\Builder  $builder
 * @param  int  $key
 * @return void
 */
protected function removeBinding($query, $key)
{
    $bindings = $query->getRawBindings()['where'];

    unset($bindings[$key]);

    $query->setBindings(array_values($bindings));
}

      

0


source


Take a look at the newQuey function (Eloquent / Model.php Lvl 4.2):

newQuery{
//'initialized' 
$b = $this->newQueryWithoutScopes();  //Get a 'clean' query. Note the plural.  
//and applies scopes
return $this->applyGlobalScopes($b);  //now builder is 'dirty'
}

      

So this offers a solution:

function someButNotAllScopes(){
$b = $this->newQueryWithoutScopes();
$unwanted = new MyUnwantedScope();
//get all scopes, but skip the one(s) you dont want
foreach($this->getGlobalScopes as $s){
 if ($s instanceof $unwanted){continue;}
  $s->apply($b, $this)
}
return $b;
}

      

You can also do something smart with scopes. Create em and OnOffInterface using applyMe method. This method can "enable" / disable the method of applying the scope. In the above function, you can get the non-automatic scale and "enable" if disabled:

$scope = $this->getGlobalScope(new Unwanted());
$scope->applyme(false); //will turn off the apply method
return $this->newQuery();  //now each scope checks if it is 'off'

      

0


source


Simply you can apply without bindings, for example:

$builder->where($column, new \Illuminate\Database\Query\Expression(1));

      

or

$builder->where($column, \DB::raw(1));

      

0


source







All Articles