跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何给ThinkPHP模型定义全局查询范围_baseScope与全局条件隔离

_baseScope 是 ThinkPHP 模型中定义的静态数组属性,仅在调用 scope() 或显式启用作用域时合并进当前模型的 select()、find() 等查询,不作用于 Db::name()、关联查询、子查询及 save()/update(),且不被 withoutGlobalScope() 影响。 全局查询范围
_baseScope
是什么,它到底在哪生效 ThinkPHP 的
_baseScope
不是“自动加载的全局过滤器”,而是一个模型类里可定义的静态属性,仅在该模型调用
scope()
或显式启用作用域时才被合并进查询条件。它不会影响
Db::name()
Model::withoutGlobalScope()
以外的任意查询,也不会穿透到关联模型或子查询中。 常见错误现象:
_baseScope
写了但没生效,或者误以为它能替代软删除中间件;还有人把它当成 Laravel 风格的全局 scope,结果发现
where()
直接调用完全绕过它。
_baseScope
必须是数组,键为字段名,值为条件值(支持
['>', 0]
等表达式) 只对当前模型的
select()
find()
count()
等主动查询方法起作用,不干预
save()
update()
若模型继承自
think\Model
且未重写
scope()
,则框架会默认读取该属性并合并进查询;但一旦你自定义了
scope()
方法,就必须手动调用
array_merge($this->_baseScope ?? [], $scope)
如何让
_baseScope
和软删除、租户隔离等条件互不干扰 多个全局条件混用时,
_baseScope
容易和
SoftDelete
行为、多租户
tenant_id
过滤产生冲突——比如软删除字段被覆盖,或租户条件被漏掉。根本原因是它们都依赖“查询前自动注入条件”,但 ThinkPHP 没有统一的 global scope 注册机制。 使用场景:SaaS 系统中,用户模型既要排除已删除记录,又要限定当前租户数据,还可能加状态过滤。 立即学习 “ PHP免费学习笔记(深入) ”; 不要把租户 ID 写进
_baseScope
,改用中间件 +
Db::listen()
动态绑定,避免命令行或队列任务误带租户上下文 软删除建议仍走
SoftDelete
行为,它内部通过
baseQuery
钩子注入,优先级高于
_baseScope
,不会被覆盖 若必须共存,把
_baseScope
改成方法
getBaseScopeAttr()
,并在其中判断当前是否处于租户上下文(如检查
app('tenant.id')
是否存在),动态返回不同数组
withoutGlobalScope()
为什么有时失效,怎么真正跳过
_baseScope
调用
withoutGlobalScope()
_baseScope
依然生效,是因为这个方法只处理通过
use \think\model\concern\SoftDelete;
等行为注册的 scope,而
_baseScope
是模型原生机制,不走同一套注册流程。 性能影响:每次查询都合并
_baseScope
是轻量操作,但若里面包含子查询或闭包(非法但有人试过),会导致 SQL 构建失败或 N+1。 要彻底跳过
_baseScope
,只能临时重置:
(new User())->setBaseScope([])->select()
更稳妥的做法是定义一个空作用域,比如
public function scopeRaw($query) {}
,然后用
User::scope('raw')->select()
,因为
scope()
调用时会清空默认合并逻辑 注意:
withoutGlobalScope()
Db::name('user')
根本无效——它只作用于模型实例,不是查询构造器 为什么别在基类模型里统一写
_baseScope = ['status' => 1]
看似省事,实则埋雷。不同子模型对
status
字段语义完全不同:用户表是“启用/禁用”,订单表是“待支付/已完成”,商品表可能是“上架/下架”。硬编码会导致查询错乱,而且无法被子类干净覆盖——PHP 不支持静态属性继承覆盖。 兼容性影响:ThinkPHP 6.0+ 中,
_baseScope
已非文档主推方式,官方倾向用
scope
方法 +
protected $scope = ['common' => 'commonScope']
配置,更可控。 基类中留空
_baseScope
,或设为
null
,强制子类显式声明 若真需统一状态字段,用 trait 封装一个
activeScope()
方法,再在各模型里
scope('active')
显式调用 调试时直接
dump((new User())->getBaseScope())
,比翻基类代码更快定位来源 最常被忽略的是:_baseScope 在模型的静态上下文中解析,所以不能访问 $this->app 或依赖容器实例——任何需要运行时判断的条件,都得绕道 scope 方法或查询监听器。

相关文章