thinkphp 全局查询范围需显式启用,不能自动生效;正确方式是定义静态 $scope 属性或重写 scope 方法返回闭包,并在查询时调用 ->scope(),basescope 仅为普通方法名,不会被自动调用。

全局查询范围在模型里怎么启用
ThinkPHP 的全局查询范围不是自动生效的,必须显式开启。默认情况下,即使你定义了 baseScope 或用 protected $scope = ['status' => ...],它也不会自动拼到每个查询里。
真正起作用的方式只有两种:继承 think\Model 并重写 scope 方法,或者在模型中定义 protected $autoWriteTimestamp = true 这类配置——但注意,后者和查询范围无关,别混淆。
- 正确做法是:在模型类中定义
protected static $scope = ['default' => ...],然后在查询时手动调用->scope('default') - 更实用的是重写
scope方法,返回闭包,在里面统一加where('status', 1)这类条件 - 如果用了软删除(
SoftDeletetrait),它的deleteTime字段会自动参与全局过滤,但这属于 trait 行为,不是模型自身的“全局查询范围”
为什么写了 baseScope 却没生效
baseScope 是 ThinkPHP 6.0+ 引入的命名约定,但它只是个方法名,不会被框架自动识别或调用。很多人以为只要叫这个名字就会被默认加载,其实完全不会。
常见错误现象:UserModel::all() 返回所有记录,包括已逻辑删除或 status=0 的数据,明明写了 public function baseScope($query)。
- 必须主动调用:比如
UserModel::scope('base')->select(),否则baseScope就是个普通方法 - 方法名可以任意,
baseScope没有特殊含义,只是社区习惯写法 - 闭包参数
$query是think\db\Query实例,可以直接链式调用where、order等,但不能用find或select
多个全局条件怎么组合不冲突
多个 scope 同时使用时,顺序很重要,后调用的会覆盖前面同字段的 where 条件。比如先 scope('status') 再 scope('tenant'),如果都写了 where('company_id', ...),后者会生效。
使用场景:多租户系统中既要过滤状态,又要绑定当前租户 ID;或者前后端分离项目中,API 统一加权限字段限制。
- 推荐把公共条件抽成独立方法,比如
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 机制。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











