我在使用全局作用域时遇到了麻烦,特别是在删除作用域时。
在我的User模型中,我有一个ActivatedUsersTrait,它引入了一个全局作用域,只查询列"activated"设置为true的用户(用户在电子邮件验证后被"激活")。
到目前为止一切都很好,当我查询User::all()时,我只得到激活=true的用户。
我现在的问题是,如何包括非激活的用户到我的查询,像SoftDeletingTrait通过withTrashed()?这只与我的ActivationController相关,我需要获得用户,设置activated=true并将它们保存回db。
我在我的ActiveUsersTrait中创建了一个withInactive()方法,基于我在SoftDeletingTrait中发现的方法,但是当我在User::withInactive->get()上运行查询时,未激活的用户不会出现在结果中。
这是我的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();
}
}
和我的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;
}
}
任何帮助都非常感谢!
提前感谢!约瑟夫。
雄辩查询现在有一个removeGlobalScopes()
方法。
参见: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();
SoftDeletingTrait的清理更简单,因为它不涉及任何绑定(它是一个"Null"的地方,而不是一个"Basic"的地方)。您遇到的问题是,[n => true]的绑定仍然存在,即使您手动删除了where.
我正在考虑做一个PR,因为我自己也遇到了同样的问题,并且没有一个很好的方法来跟踪哪个位置和哪个绑定在一起。
如果您只使用一个简单的查询,您可以或多或少地像这样跟踪绑定的索引:
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);
}
}
注意我们是如何将索引存储在添加了where子句和绑定的地方,而不是遍历并检查是否找到了正确的索引。这似乎是一种更好的设计——我们添加了where子句和绑定,所以我们应该知道它在哪里,而不必遍历所有where子句。当然,如果其他东西(比如::withTrashed
)也扰乱了where数组,它就会变得混乱。不幸的是,where绑定和where子句只是平面数组,因此我们不能准确地侦听它们的变化。更倾向于采用一种面向对象的方法,更好地自动管理子句及其绑定之间的依赖关系。
显然,这种方法可以受益于一些更漂亮的代码和数组键存在的验证等。但这应该让你开始。由于全局作用域不是单例的(它们在newQuery()
被调用时被应用),这种方法应该是有效的,而不需要额外的验证。
希望在"现在足够好"的标题下有所帮助!
在遇到同样的问题后发现了这个,我有一个更有说服力的解决方案。只需将你的"remove"方法替换为:
/**
* 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));
}
看看newqueue函数(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'
}
所以,这建议了一个解决方案:
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;
}
你也可以用作用域做一些聪明的事情。用'applyMe'方法实现OnOffInterface。这个方法可以"打开"/关闭作用域的apply方法。在上面的函数中,您可以获得不需要的作用域并将其'关闭':
$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'
简单地说,您可以像这样应用不需要绑定:
$builder->where($column, new 'Illuminate'Database'Query'Expression(1));
或
$builder->where($column, 'DB::raw(1));