Laravel:如何“禁用”全局范围以将“非活动”对象包含在查询中?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25312386/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 09:56:41  来源:igfitidea点击:

Laravel: how to "disable" a global scope in order to include "inactive" objects into query?

phplaraveleloquent

提问by jsphpl

I am having trouble with global scopes, especially the removal of the scope.

我在使用全局范围时遇到问题,尤其是删除范围。

In my User model, i have a ActivatedUsersTrait, that introduces a global scope to only query for Users with the column "activated" set to true (The User is "activated" after email verification).

在我的用户模型中,我有一个 ActivatedUsersTrait,它引入了一个全局范围,仅查询“已激活”列设置为 true 的用户(用户在电子邮件验证后“已激活”)。

So far everything works fine, when i query for User::all(), i only get Users with activated=true.

到目前为止一切正常,当我查询 User::all() 时,我只得到了激活 = true 的用户。

My problem now is, how to include the non-activated Users into my query, like SoftDeletingTrait does via withTrashed()? This is only relevant in my ActivationController, where i need to get the User, set activated=true and save them back to db.

我现在的问题是,如何将未激活的用户包含到我的查询中,就像 SoftDeletingTrait 通过 withTrashed() 所做的那样?这仅与我的 ActivationController 相关,我需要在其中获取用户,设置激活 = true 并将它们保存回数据库。

I've created a withInactive() method in my ActiveUsersTrait, based on the method i found in SoftDeletingTrait, but when i run a query on User::withInactive->get(), the non-activated Users won't show up in the results.

我在我的 ActiveUsersTrait 中创建了一个 withInactive() 方法,基于我在 SoftDeletingTrait 中找到的方法,但是当我在 User::withInactive->get() 上运行查询时,未激活的用户不会出现在结果。

Here's my ActiveUsersTrait:

这是我的 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:

和我的 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 highly appreciated!

任何帮助表示高度赞赏!

Thanks in advance! -Joseph

提前致谢!-约瑟夫

回答by voidstate

Eloquent queries now have a removeGlobalScopes()method.

Eloquent 查询现在有了一个removeGlobalScopes()方法。

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

请参阅:https: //laravel.com/docs/5.3/eloquent#query-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();

回答by DefiniteIntegral

The SoftDeletingTrait where cleanup is simpler because it doesn't involve any bindings (it's a "Null" where, not a "Basic" where). The issue you're encountering is that the binding for [ n => true ] is still there, even when you manually remove the where.

SoftDeletingTrait 的清理更简单,因为它不涉及任何绑定(它是“Null”,而不是“Basic”)。您遇到的问题是 [ n => true ] 的绑定仍然存在,即使您手动删除了 where。

I'm thinking about making a PR because I encountered the same issue myself, and there isn't a great way to keep track of which wheres and which bindings go together.

我正在考虑做一个 PR,因为我自己也遇到了同样的问题,并且没有一个很好的方法来跟踪哪些地方和哪些绑定在一起。

If you are only using a simple query, you can keep track of the index of the binding more or less like so:

如果您只使用一个简单的查询,您可以或多或少地跟踪绑定的索引,如下所示:

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're storing the indices where the where clause and bindings were added, rather than looping through and checking if we found the right one. This almost feels like a better design—we added the where clause and binding, so we should know where it is without having to loop through all where clauses. Of course, it will all go haywire if something else (like ::withTrashed) is also messing with the where array. Unfortunately, the where bindings and where clauses are just flat arrays, so we can't exactly listen on changes to them. A more object-oriented approach with better automatic management of the dependency between clauses and their binding(s) would be preferred.

请注意我们如何存储添加 where 子句和绑定的索引,而不是循环遍历并检查我们是否找到了正确的索引。这几乎感觉像是一个更好的设计——我们添加了 where 子句和绑定,所以我们应该知道它在哪里,而不必遍历所有 where 子句。当然,如果其他东西(如::withTrashed)也弄乱了 where 数组,一切都会变得混乱。不幸的是, where 绑定和 where 子句只是平面数组,所以我们不能准确地监听它们的变化。更好地自动管理子句及其绑定之间的依赖关系的更面向对象的方法将是首选。

Obviously this approach could benefit from some prettier code and validation that array keys exists, etc. But this should get you started. Since the global scopes aren't singletons (they get applied whenever newQuery()is invoked) this approach should be valid without that extra validation.

显然,这种方法可以从一些更漂亮的代码和数组键存在的验证等中受益。但这应该会让你开始。由于全局作用域不是单例(它们在每次newQuery()被调用时都会被应用),因此这种方法应该是有效的,无需额外的验证。

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

希望这有助于在“现在足够好”的标题下!

回答by John Mellor

Just found this after having the same problem and I have a more eloquent solution. Simply 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));
}

回答by tweaker

Look at the newQuey function (Eloquent/Model.php Lvl 4.2):

看newQuey函数(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 suggests 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 clever with scopes. Make em implement and OnOffInterface with an 'applyMe' method. This method can 'turn' on/off the apply method of a scope. In the function above you can get the unswanted scope and 'turn' if off:

你也可以用范围做一些聪明的事情。使用“applyMe”方法使 em 实现和 OnOffInterface。此方法可以“打开”/关闭作用域的应用方法。在上面的函数中,您可以获得不需要的范围,如果关闭则“关闭”:

$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'

回答by f.n.

Simply, you can apply without bindings like so:

简单地说,您可以在没有绑定的情况下进行申请,如下所示:

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

or

或者

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