全局作用域自动影响所有查询,本地作用域需显式调用;全局在boot()注册后对Model::all()等全部生效,不可直接调用auth()或request();本地作用域是scope开头的链式方法,必须return $query。

全局作用域会自动影响所有查询,本地作用域必须显式调用;选错类型会导致条件漏加、绕过失败或测试难覆盖。
全局作用域注册后所有查询都生效
全局作用域在模型 boot() 中注册,之后所有 Model::all()、Model::find()、Model::where(...)->get() 都会被注入条件。它不区分上下文,也不检查当前是否在 HTTP 请求中——所以不能在 apply() 里直接调用 auth() 或 request()。
- 闭包形式注册时,必须用字符串键名(如
'tenant'),否则无法用withoutGlobalScope('tenant')移除 - 类形式注册需实现
Illuminate\Database\Eloquent\Scope接口,且apply()方法里只操作传入的$builder和$model - 多个全局作用域叠加时,执行顺序按
addGlobalScope()调用顺序,但彼此不可知,避免互相依赖
本地作用域是链式调用的“快捷方法”
本地作用域本质是模型里的普通方法,命名以 scope 开头(如 scopeActive()),Eloquent 会在链式调用时自动把 $query 作为第一个参数传入。它不会自动触发,必须显式写成 Model::active()->get()。
- 方法必须
return $query->xxx,漏掉return会导致后续链式调用中断 - 支持传参,比如
scopeOfType($query, $type),但参数无类型校验,需自行判断空值或非法值 - 不能在
boot()或静态初始化阶段调用,只能用于构建查询时
事务中用全局作用域要固化上下文
在 DB::transaction() 内部,请求上下文可能已销毁,auth()->id() 可能返回 null。全局作用域一旦注册,就无法再动态改值。
- 推荐做法:在事务外获取必要值(如
$tenantId = auth()->id()),再用闭包 +use ($tenantId)绑定 - 不推荐:在作用域类的
apply()中调用auth(),这在队列、命令行或测试中必然失败 - 更稳妥的替代:事务内统一用
withoutGlobalScopes(),然后对每个查询手动加where('tenant_id', $tenantId)
排除全局作用域时名称必须严格匹配
withoutGlobalScope() 不是模糊匹配,它依赖注册时的标识符。闭包注册用了字符串名,类注册用了类名,二者不能混用。
- 闭包注册:
addGlobalScope('active', function ($builder) { ... })→ 排除用withoutGlobalScope('active') - 类注册:
addGlobalScope(new ActiveScope)→ 排除用withoutGlobalScope(ActiveScope::class) - 想一次移除多个?传数组:
withoutGlobalScope([ActiveScope::class, 'tenant'])
最容易被忽略的是:全局作用域在关联查询中也会生效,比如 User::with('posts'),posts 查询同样会被加上全局 where 条件——如果你没在 Post 模型里定义对应作用域,这个行为可能出乎意料。











