局部作用域必须以scope开头、首参为$query并返回$query,全局作用域须在booted中用addGlobalScope注册;选错类型会导致条件漏加或绕过失效,调用不匹配则报方法未定义。

局部作用域必须显式调用才生效,全局作用域一旦注册就自动影响所有查询——选错类型会导致条件漏加、绕过失败或测试难覆盖。
scope开头的方法怎么写才有效
局部作用域本质是模型里的普通方法,但 Eloquent 只认以 scope 开头、且第一个参数为 $query(Illuminate\Database\Eloquent\Builder 实例)的方法。漏掉前缀或参数顺序错,调用时直接报 Call to undefined method。
- 方法名必须是
scopeActive、scopeOfType这种格式,调用时写成->active()或->ofType('admin') - 第一个参数固定是
$query,后续才是自定义参数,例如scopePublishedAfter($query, $date) - 必须
return $query,不能return $query->get()或return null,否则链式调用中断 - 别在作用域里调用
$this->where()——模型实例没有where方法,只有$query有
全局作用域为什么注册了却没生效
全局作用域必须在模型的 booted(或旧版 boot)静态方法中调用 addGlobalScope() 才会绑定。写在构造函数、属性初始化、或任意其他位置,都不会触发。
- 类形式注册:用
static::addGlobalScope(new ActiveScope),排除时必须传ActiveScope::class - 闭包形式注册:必须带字符串键名,如
static::addGlobalScope('active', function ($builder) { ... }),否则无法用withoutGlobalScope('active')移除 - 多个全局作用域叠加时,执行顺序按
addGlobalScope()调用顺序,后注册的包裹在外层,类似中间件洋葱模型 - 软删除(
SoftDeletes)本身就是一个全局作用域,它会和你的作用域嵌套,注意where deleted_at is null和你写的where status = 'active'的逻辑层级
什么时候该用 when() 而不是直接链式调用作用域
当某个查询条件是否启用取决于运行时变量(比如搜索表单提交了关键词、或 API 请求带了 include_draft=1),硬编码链式调用会污染查询逻辑,也容易出空值错误。
-
when()是惰性执行:条件为 false 时,整个闭包被跳过,不会拼进 SQL - 适合组合动态参数,例如
User::when($request->filled('city'), fn($q) => $q->city($request->city))->get() - 避免在作用域内部做请求/认证判断——
auth()或request()在队列、命令行、测试中不可用,应在调用方取好值再传入 - 如果只是简单开关,
when()比写一堆if+ 多次->where()更语义清晰
如何安全地临时绕过全局作用域
withoutGlobalScope() 和 withoutScopes() 都只对当前查询链生效,但行为差异极大,用错会导致查出不该出现的数据(比如已删除记录)。
-
withoutGlobalScope(ActiveScope::class):只剔除指定类的作用域,软删除等其它全局作用域仍保留 -
withoutGlobalScope('active'):仅适用于闭包注册时用了字符串键名的场景,名称必须严格匹配 -
withoutScopes():清空所有全局作用域,包括 Laravel 自带的软删除约束,结果可能含deleted_at IS NOT NULL的记录 - 事务中慎用全局作用域——
auth()->id()在 DB transaction 内可能为 null,推荐做法是在事务外取好上下文值(如$tenantId),再用where('tenant_id', $tenantId)手动加条件
最常被忽略的是作用域的“生效边界”:局部作用域不调用等于没写,全局作用域注册后连 find() 和关联预加载(with())都会被影响;而 withoutGlobalScope() 的参数必须和注册时完全一致,类名、字符串键、大小写,一个都不能错。


















