Laravel中如何设置全局作用域来自动过滤特定数据?

2026-05-07 18:271阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计802个文字,预计阅读时间需要4分钟。

Laravel中如何设置全局作用域来自动过滤特定数据?

全局

GlobalScope 是什么,为什么它总在“看不见的地方”起作用

它不是一个配置项,也不是中间件,而是一个实现了 Illuminate\Database\Eloquent\Scope 接口的类,在模型 boot() 阶段通过 addGlobalScope() 注册后,就会被 Eloquent 查询构建器自动调用。它的 apply() 方法接收 BuilderModel 实例,允许你直接修改 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分钟。

Laravel中如何设置全局作用域来自动过滤特定数据?

全局

GlobalScope 是什么,为什么它总在“看不见的地方”起作用

它不是一个配置项,也不是中间件,而是一个实现了 Illuminate\Database\Eloquent\Scope 接口的类,在模型 boot() 阶段通过 addGlobalScope() 注册后,就会被 Eloquent 查询构建器自动调用。它的 apply() 方法接收 BuilderModel 实例,允许你直接修改 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 或模型绑定,而不是运行时猜测。