全局作用域
所谓「全局作用域」,指的是预置过滤器在注册该「全局作用域」的模型类的所有查询中生效,不需要指定任何额外条件。
以 User
模型类为例,我们在系统中可能只想针对已经验证过邮箱的用户进行操作,在没有介绍「作用域」之前,可能你会在应用中到处编写这样的代码:
$users = User::whereNotNull('email_verified_at')->...
通过全局作用域类实现
要实现「全局作用域」,首先需要编写一个实现 Illuminate\Database\Eloquent\Scope
接口的全局作用域类,这里我们将其命名为 EmailVerifiedAtScope
,并将其放到 app/Scopes
目录下:
<?php
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class EmailVerifiedAtScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
return $builder->whereNotNull('email_verified_at');
}
}
在这个全局作用域类中,只需要实现 apply
方法即可,在该方法中,在查询构建器上应用过滤器方法并将其返回。
然后,我们需要将这个全局作用域类注册到 User
模型类上,这样,在 User
模型类上进行查询的时候才可以应用相应的过滤条件。这个工作可以通过在 User
模型类中重写父类的 boot
方法来完成:
protected static function boot()
{
parent::boot();
static::addGlobalScope(new EmailVerifiedAtScope());
}
注:boot
方法会在模型类实例化的时候调用。你可以在这里进行一些模型类的初始化操作。
通过匿名函数实现
如果你觉得编写一个「全局作用域」类很麻烦,过滤逻辑又很简单,还可以在模型类的 boot
方法中通过匿名函数实现全局作用域:
protected static function boot()
{
parent::boot();
//static::addGlobalScope(new EmailVerifiedAtScope());
static::addGlobalScope('email_verified_at_scope', function (Builder $builder) {
return $builder->whereNotNull('email_verified_at');
});
}
实现效果和上面通过全局作用域类完全一样。
移除全局作用域
在某些特定场景下,我们可能需要移全局作用域,比如在后台用户管理页,我们需要将未验证邮箱的用户页显示出来,这个时候我们可以借助模型类的 withoutGlobalScope
方法来实现,该方法支持多种传参格式,移除多种全局作用域及其组合:
User::withoutGlobalScope(EmailVerifiedAtScope::class)->get(); # 指定类
User::withoutGlobalScope('email_verified_at_scope')->get(); # 匿名函数
User::withoutGlobalScopes()->get(); # 移除所有全局作用域
User::withoutGlobalScopes([FirstScope::class, SecondScope::class])->get(); # 移除多个类/匿名函数
局部作用域
所谓「局部作用域」,指的是预置过滤器在对应模型类的指定查询中生效,与「全局作用域」不同,「局部作用域」需要额外指定才能生效,但是相应的,也更加灵活,可以适用于不同场景。
「局部作用域」的实现也比较简单,在需要应用它的模型类中定义一个过滤器方法即可。该方法需要以 scope
开头,然后附加该过滤器的名称,以文章列表页显示最流行文章为例(按照浏览数逆序),可以在 Post
模型类中编写一个 scopePopular
方法:
public function scopePopular(Builder $query)
{
return $query->where('views', '>', '0')->orderBy('views', 'desc');
}
而在文章详情页,我们希望展示的是已发布的文章详情,如果文章没有发布,返回 404,因此我们再定义一个「局部作用域」方法 scopeActive
:
public function scopeActive(Builder $query)
{
return $query->where('status', Post::ACTIVED);
}
在模型类上调用「局部作用域」过滤器方法只需调用 scope
之后的过滤器名称即可,Eloquent 底层会通过魔术方法自动调用对应完整方法:
$post = Post::active()->find(100);
$post = Post::active()->popular()->get();
动态作用域
此外,Eloquent 模型类还支持「动态作用域」,所谓动态作用域指的是在查询过程中动态设置预置过滤器的查询条件,动态作用域和局部作用域类似,过滤器方法名同样以 scope
开头,只不过可以通过额外参数指定查询条件,比如我要在文章中查询指定类型的文章,可以通过在 Post
模型类中定义如下方法:
public function scopeOfType(Builder $query, $type)
{
return $query->where('type', $type);
}
这样,在查询指定类型的文章时,就可以这么实现:
$posts = Post::active()->ofType(Post::Article)->get();