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