全局作用域是Laravel模型层面自动注入的查询条件,只要使用该模型进行Eloquent查询(where、get、find等),就会无感地加上你定义的约束。

什么是全局作用域,它在什么场景下必须用
全局作用域是 Laravel 模型层面自动注入的查询条件,只要使用该模型进行 Eloquent 查询(where、get、find 等),就会无感地加上你定义的约束。典型场景包括:多租户系统中自动过滤 tenant_id,软删除之外的业务级状态隔离(如只查 status = 'active'),或租户内数据自动绑定当前用户所属组织。
注意:它不是中间件或服务层逻辑,而是直接嵌入到 Eloquent 的查询构建器中,对所有静态/实例查询生效(除非显式移除)。
如何定义一个可复用的全局作用域类
推荐用独立类实现 Illuminate\Database\Eloquent\Scope 接口,避免闭包作用域带来的测试与维护问题。关键点在于 apply() 方法必须调用 $builder->where() 或类似方法修改查询构建器。
- 类必须实现
apply()方法,接收$builder和$model两个参数 - 不要在
apply()中调用$builder->get()或执行查询——那会破坏链式调用 - 若需动态值(如当前租户 ID),应在作用域构造时传入,而非在
apply()中读取全局状态(如session()或auth()),否则会导致队列任务或 API 请求上下文错乱
namespace App\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class TenantScope implements Scope
{
protected $tenantId;
public function __construct($tenantId)
{
$this->tenantId = $tenantId;
}
public function apply(Builder $builder, Model $model)
{
$builder->where('tenant_id', $this->tenantId);
}
}
如何在模型中注册全局作用域
在模型的 booted() 方法中调用 static::addGlobalScope()。注意不是 boot(),因为 booted() 是模型类加载完成后的钩子,确保作用域注册时机正确。
- 若作用域类带构造参数(如上例的
$tenantId),必须在booted()中获取该值(例如从请求上下文、服务容器或配置中读取) - 多个全局作用域按注册顺序叠加,但彼此独立;若逻辑冲突(如都改
where同一字段),后注册的会覆盖先注册的 - 不能在模型构造函数中注册,否则每次 new Model() 都会重复添加,导致作用域被多次应用
use App\Scopes\TenantScope;
class Post extends Model
{
protected static function booted()
{
$tenantId = request()->user()?->tenant_id ?? config('app.default_tenant');
static::addGlobalScope(new TenantScope($tenantId));
}
}
如何临时取消某个全局作用域
用 withoutGlobalScope() 可跳过单个作用域,withoutGlobalScopes() 则跳过全部。这在后台管理、数据迁移或调试时非常必要——否则连 php artisan tinker 查数据都会被拦截。
-
withoutGlobalScope(TenantScope::class):只移除指定类的作用域 -
withoutGlobalScopes():彻底清空所有全局作用域(含 Laravel 自带的软删除) - 不支持按名字移除(如字符串标识),必须传类名或作用域实例
- 这个方法只对当前查询链有效,不影响后续其他查询
// 跳过租户作用域查所有文章 $allPosts = Post::withoutGlobalScope(TenantScope::class)->get(); // 完全绕过所有全局作用域(含软删除) $forceAll = Post::withoutGlobalScopes()->get();
真正容易被忽略的是:全局作用域对 count()、exists()、pluck() 等所有基于 Builder 的方法都生效,而不仅是 get()。如果忘记这点,在统计总数或判断是否存在时可能得到错误结果。










