本地作用域需以scope开头、public修饰、首参$query、返回$query;全局作用域闭包适合简单条件,独立类适合复用场景;withoutglobalscopes()会移除所有全局作用域包括softdeletingscope。

全局作用域会自动加到所有查询里,本地作用域得手动调用才生效——选哪个,取决于你是否希望“默认过滤”成为模型行为的一部分。
本地作用域怎么写才不会报 Call to undefined method 错误
最常见的错误是方法名没加 scope 前缀、没用 public 修饰符,或者参数写错。
- 方法必须以
scope开头,比如scopeActive,调用时写->active() - 第一个参数固定是
$query(类型为Illuminate\Database\Eloquent\Builder),不能是$this或漏掉 - 必须返回
$query,否则链式调用中断,比如User::active()->orderBy(...)会失败 - 带参数的作用域,外部值必须显式传入,不能在方法体内读
request()或auth()
示例:
public function scopeWithinDays($query, int $days = 7)
{
return $query->where('created_at', '>=', now()->subDays($days));
}
// 调用:Order::withinDays(30)->get()
全局作用域该用闭包还是独立类
闭包适合一次性、无状态的简单条件(比如固定 where('published', 1));独立类更适合需要传参、复用、测试或跨模型共享的场景(比如租户 ID 隔离)。
- 闭包注册时必须指定字符串键名:
addGlobalScope('published', function ($builder) { ... }),否则无法用withoutGlobalScope('published')移除 - 独立类要实现
Illuminate\Database\Eloquent\Scope接口,apply()方法里只改$builder,别执行查询 - 构造参数(如
$tenantId)必须在booted()中获取并传入,不能在apply()里动态读 session 或 auth —— 队列任务里会出错 - Laravel 9+ 推荐用
booted()而非boot()注册,时机更稳妥
为什么 sometimes withoutGlobalScopes() 不起作用
常见原因是作用域注册方式和移除方式不匹配,或者被其他作用域干扰。
- 用字符串名注册的闭包作用域,必须用同名字符串移除:
withoutGlobalScope('published'),不是withoutGlobalScope('PublishedScope') -
withoutGlobalScopes()只移除全局作用域,不影响本地作用域(scope*方法) - 如果模型同时用了软删除(
SoftDeletestrait),它的SoftDeletingScope也是全局作用域,withoutGlobalScopes()会一并干掉它 —— 这可能导致find(1)查不到已软删记录,但你本意只是绕过业务级作用域 - 多个全局作用域叠加时,后注册的 where 条件可能覆盖前面的同字段条件,
withoutGlobalScopes()是全量清除,没法只保留部分
真正容易被忽略的是:全局作用域在 find()、findOrFail()、first() 这些方法里照样生效,不是只有 get() 才触发;而本地作用域不支持静态调用以外的方式,比如不能在关系定义里直接写 ->active(),得靠 with(['posts' => function ($q) { $q->active(); }]) 这种闭包形式。











