ThinkPHP模型怎么使用全局查询范围_ThinkPHP默认条件复用指南【指南】
ThinkPHP全局查询范围必须通过重写scope方法返回闭包或手动调用scope()方法才能启用,baseScope仅仅是命名约定不会自动生效。软删除依赖SoftDeletetrait而非模型自身的查询范围,多个scope的调用顺序影响条件覆盖,且关联模型不会继承父模型的scope。
先说几个核心判断:ThinkPHP的全局查询范围(Global Scope)并不是开箱即用、自动生效的机制。很多开发者误以为定义了一个叫baseScope的方法,或者简单地在模型里配置了$scope属性,就能让所有查询自动带上条件——这是最常见也是最容易踩的坑。
实际上,真正让查询范围生效的方式只有两种:要么在模型类中重写scope方法,返回一个闭包;要么在每次查询时,手动调用->scope()方法。没有第三条路。至于baseScope这个名字,它只是社区约定俗成的一个命名习惯,框架本身并不会对它做任何特殊处理。说得直白点,它就是个普通方法,你不主动调用,它永远不会被执行。
软删除倒是例外——SoftDelete trait会自动把delete_time字段的逻辑加到全局查询中,但那是trait自带的行为,跟模型自身的“全局查询范围”不是一回事。

全局查询范围在模型里怎么启用
要启用全局查询范围,必须走下面两种路径之一:
- 在模型类中用
protected static $scope = ['default' => ...]定义范围数组,然后在查询时通过->scope('default')手动调用。注意,这里的关键词是“手动”——框架不会自动帮你加。 - 更推荐的做法是重写模型的
scope方法,让它在内部返回一个闭包。这个闭包里统一写where('status', 1)这类条件,调用时也只需要->scope()即可。 - 如果用了
SoftDeletetrait,它的deleteTime字段会自动参与全局过滤,但这属于trait行为,不是模型自身的“全局查询范围”。
为什么写了baseScope却没生效
这个问题问的人最多。真相很简单:baseScope是ThinkPHP 6.0+引入的一个命名约定,但它只是一个方法名,框架不会自动识别或调用它。
典型的翻车现场:你写了public function baseScope($query),然后直接用UserModel::all()查数据,结果发现所有记录都返回了,包括那些本该被过滤掉的逻辑删除数据或status=0的记录。为什么?因为baseScope根本没被触发。
- 正确用法是:
UserModel::scope('base')->select(),否则baseScope就是个普通方法,跟testScope、fooScope没有任何区别。 - 方法名可以任意命名,
baseScope本身没有特殊含义,只是社区习惯写法。 - 闭包参数
$query是think\db\Query实例,可以链式调用where、order等,但不能在闭包里直接执行find或select——这是闭包,不是查询入口。
多个全局条件怎么组合不冲突
当多个scope同时使用时,执行顺序变得至关重要。后调用的scope会覆盖前面同字段的where条件。举个例子:如果先调用scope('status')再调用scope('tenant'),而两者都写了where('company_id', ...),那么最终生效的是后者的条件。
这个特性在多租户系统中特别有用——既要过滤状态,又要绑定当前租户ID;或者前后端分离项目中,API需要统一加权限字段限制。但前提是,你得清楚每个scope的覆盖规则。
- 推荐把公共条件抽成独立方法,比如
addTenantScope($query),然后在各个scope中调用,避免重复写where('tenant_id', ...)。 - 不要在scope里用
$query->when(...)做动态判断——它只对当前查询有效,无法复用到关联查询中。 - 关联模型(如
hasMany)不会自动继承父模型的scope,必须在关联定义里显式加->scope('default')。这一点经常被忽略,导致关联数据中间出现了不该出现的记录。
性能和兼容性要注意什么
全局查询范围本质上是给SQL多加WHERE条件,听起来简单,但在分页、统计、关联预载等场景下,很容易出问题或拖慢查询速度。
最典型的问题:UserModel::with('posts')->paginate()分页总数不准,因为子查询没走scope;或者count()结果比select()多,原因可能是count走了缓存,或者根本没触发scope。
- 务必在
count()、sum()等聚合查询前补上scope,例如UserModel::scope('default')->count()。 - TP6.1+支持
useSoftDelete配置,开启后软删除字段会自动加入所有查询,但仅限delete_time字段,其他字段仍需手写scope。 - 如果你用的是MySQL 8+的CTE或窗口函数,scope里加的条件可能干扰执行计划,建议用
explain看实际生成的SQL,确认是否出现了预期外的过滤条件。
最后,最容易被忽略的一点:scope只影响模型自身的主表查询,对view、union、原生query完全无效。如果真要复用条件,就得把where逻辑单独封装成工具函数,而不是依赖scope机制。这一点,在复杂查询场景下尤其重要。


































