Laravel中如何设置全局作用域来自动过滤特定数据?
- 内容介绍
- 文章标签
- 相关推荐
本文共计802个文字,预计阅读时间需要4分钟。
全局
GlobalScope 是什么,为什么它总在“看不见的地方”起作用
它不是一个配置项,也不是中间件,而是一个实现了 Illuminate\Database\Eloquent\Scope 接口的类,在模型 boot() 阶段通过 addGlobalScope() 注册后,就会被 Eloquent 查询构建器自动调用。它的 apply() 方法接收 Builder 和 Model 实例,允许你直接修改 SQL 条件。
常见现象包括:
- 明明数据库里有 status = 0 的记录,
User::all()却查不到 - 后台需要查看“已删除”用户,但
User::withTrashed()不生效——可能是因为自定义作用域和SoftDeletingScope冲突 - 多租户系统中,
Order::where('id', 123)->first()返回 null,实际是 tenant_id 没匹配上,但错误不报
如何正确注册一个 GlobalScope(以租户隔离为例)
不能在控制器或服务里临时加 where,必须从模型层统一控制。推荐使用独立作用域类而非闭包,便于复用和测试。
- 运行
php artisan make:scope TenantScope创建类 - 在
apply()中注入当前租户 ID,不要硬编码:$builder->where('tenant_id', request()->user()?->tenant_id ?? 1)(注意:真实项目应从认证上下文或请求属性取值,而非直接依赖request()) - 在模型
boot()中注册:static::addGlobalScope(new TenantScope($tenantId));若需动态传参,建议改用构造函数注入或从容器解析 - 避免在
apply()中做复杂计算或 DB 查询,否则每次all()都会触发额外开销
怎么临时跳过某个 GlobalScope(而不是全部)
用 withoutGlobalScope(),不是 withoutGlobalScopes()。后者会清空所有作用域(包括软删除、语言、状态过滤),极危险。
- 跳过租户作用域:
User::withoutGlobalScope(TenantScope::class)->get() - 跳过软删除作用域(即查出已删数据):
User::withoutGlobalScope(\Illuminate\Database\Eloquent\SoftDeletingScope::class)->get(),这和withTrashed()等价 - 多个作用域要跳过?链式调用:
->withoutGlobalScope(A::class)->withoutGlobalScope(B::class) - 别在模型基类里重写
newQuery()默认移除作用域——那等于主动关闭安全围栏
容易被忽略的关键细节
全局作用域对 exists()、value()、pluck() 同样生效,但它**不改变模型实例的属性或行为**;restore()、forceDelete() 等写操作也不受其影响。最常踩的坑是:在作用域里用了未加载的关系或未初始化的上下文(比如 auth()->user() 在队列任务中为 null),导致条件恒为 false 或抛出异常。真正可靠的租户 ID 应来自请求头、JWT payload 或模型绑定,而不是运行时猜测。
本文共计802个文字,预计阅读时间需要4分钟。
全局
GlobalScope 是什么,为什么它总在“看不见的地方”起作用
它不是一个配置项,也不是中间件,而是一个实现了 Illuminate\Database\Eloquent\Scope 接口的类,在模型 boot() 阶段通过 addGlobalScope() 注册后,就会被 Eloquent 查询构建器自动调用。它的 apply() 方法接收 Builder 和 Model 实例,允许你直接修改 SQL 条件。
常见现象包括:
- 明明数据库里有 status = 0 的记录,
User::all()却查不到 - 后台需要查看“已删除”用户,但
User::withTrashed()不生效——可能是因为自定义作用域和SoftDeletingScope冲突 - 多租户系统中,
Order::where('id', 123)->first()返回 null,实际是 tenant_id 没匹配上,但错误不报
如何正确注册一个 GlobalScope(以租户隔离为例)
不能在控制器或服务里临时加 where,必须从模型层统一控制。推荐使用独立作用域类而非闭包,便于复用和测试。
- 运行
php artisan make:scope TenantScope创建类 - 在
apply()中注入当前租户 ID,不要硬编码:$builder->where('tenant_id', request()->user()?->tenant_id ?? 1)(注意:真实项目应从认证上下文或请求属性取值,而非直接依赖request()) - 在模型
boot()中注册:static::addGlobalScope(new TenantScope($tenantId));若需动态传参,建议改用构造函数注入或从容器解析 - 避免在
apply()中做复杂计算或 DB 查询,否则每次all()都会触发额外开销
怎么临时跳过某个 GlobalScope(而不是全部)
用 withoutGlobalScope(),不是 withoutGlobalScopes()。后者会清空所有作用域(包括软删除、语言、状态过滤),极危险。
- 跳过租户作用域:
User::withoutGlobalScope(TenantScope::class)->get() - 跳过软删除作用域(即查出已删数据):
User::withoutGlobalScope(\Illuminate\Database\Eloquent\SoftDeletingScope::class)->get(),这和withTrashed()等价 - 多个作用域要跳过?链式调用:
->withoutGlobalScope(A::class)->withoutGlobalScope(B::class) - 别在模型基类里重写
newQuery()默认移除作用域——那等于主动关闭安全围栏
容易被忽略的关键细节
全局作用域对 exists()、value()、pluck() 同样生效,但它**不改变模型实例的属性或行为**;restore()、forceDelete() 等写操作也不受其影响。最常踩的坑是:在作用域里用了未加载的关系或未初始化的上下文(比如 auth()->user() 在队列任务中为 null),导致条件恒为 false 或抛出异常。真正可靠的租户 ID 应来自请求头、JWT payload 或模型绑定,而不是运行时猜测。

