Laravel局部作用域是模型中以scope开头、返回查询构建器的方法,用于封装可复用的查询条件,解决控制器中重复写where逻辑的问题;必须以scope前缀命名且返回$query,支持参数但首参固定为$query。

什么是 Laravel 局部作用域,它解决什么问题
局部作用域(Local Scopes)是 Laravel 模型里封装常用查询条件的机制,不是语法糖,而是为避免在控制器或服务类里反复写 where('status', 'active')->whereNotNull('published_at') 这类重复逻辑。它本质是模型上的一个约定方法,必须以 scope 开头,且第一个参数固定为 $query(即 Builder 实例)。
常见错误现象:写了 active() 方法但没加 scope 前缀,调用时直接报错 Call to undefined method;或者返回了数组或模型实例,而不是继续返回 $query,导致链式调用中断。
- 必须以
scope开头,如scopeActive(),调用时才可省略scope,写成->active() - 方法必须返回
$query,不能 return $query->get() 或 return $models - 不支持静态调用(
User::active()是允许的,但那是 Eloquent 的魔法,底层仍走实例化查询构造器)
怎么写带参数的局部作用域
很多场景下条件不是固定的,比如按时间范围筛选、按状态值动态匹配。这时作用域需要接收额外参数,但注意:第一个参数永远是 $query,后续才是自定义参数。
典型错误:把参数放在第一位,或在作用域里直接调用 $this->where(...) —— 模型实例上没有 where,只有 Builder 有。
- 正确签名是
scopePublishedAfter($query, $date),调用为->publishedAfter('2024-01-01') - 参数类型要自己校验,Laravel 不做自动转换,传入字符串却用
Carbon::parse()处理更安全 - 避免在作用域里做复杂计算或 DB 查询,它只负责拼接 SQL 条件;关联预加载、分页等应留在调用方
public function scopePublishedAfter($query, $date)
{
return $query->where('published_at', '>=', Carbon::parse($date)->startOfDay());
}
全局作用域 vs 局部作用域,什么时候该选哪个
局部作用域是“按需显式调用”,全局作用域(Global Scopes)是“自动注入到每个查询”。别因为想复用就一股脑全塞进局部作用域——有些逻辑确实该全局统一,比如软删除的 whereNull('deleted_at')。
容易踩的坑:把多租户的 where('tenant_id', tenant()->id) 写成局部作用域,结果每次都要手动加 ->forTenant(),漏写就查出脏数据;反过来,把「仅管理员可见」这种业务级筛选写成全局作用域,又会导致普通用户查不到本该看到的数据。
- 局部作用域适合:业务语义明确、非强制、可组合的条件,如
->draft()、->byUser($id) - 全局作用域适合:基础设施级约束,所有查询都必须遵守,如租户隔离、状态过滤(如只查启用项)、软删除
- 全局作用域一旦注册,无法被局部作用域绕过(除非用
withoutGlobalScopes()),而局部作用域完全可控
作用域嵌套和链式调用的边界在哪
多个局部作用域可以连着写,比如 User::active()->verified()->recent()->get(),看起来很顺,但要注意底层仍是线性拼接 WHERE 条件,不会自动去重或合并逻辑。一旦某个作用域内部用了 orWhere,就可能破坏整个链路的布尔逻辑。
常见错误现象:写了 scopeSearch($query, $q) 里用 $query->orWhere(...)->orWhere(...),结果跟前面的 ->active() 变成 WHERE active = 1 OR name LIKE ... OR email LIKE ...,完全偏离预期。
- 优先用
where,慎用orWhere;必须用时,用闭包分组:$query->where(function ($q) { $q->where(...)->orWhere(...); }) - 作用域之间不共享变量或状态,不要试图在
scopeA()里修改属性影响scopeB() - 调试时可用
toSql()看最终 SQL,比猜逻辑靠谱得多:User::active()->toSql()
作用域看着简单,真正难的是判断“这个条件到底属不属于模型职责”——它不该承载权限校验、缓存策略或跨库关联逻辑。越往业务深处走,越容易把本该在 Service 层做的决定,硬塞进模型里。留个心眼:当一个作用域开始需要访问 Request、Auth 或配置项时,基本就该往外挪了。


















