ThinkPHP的scope是模型中以scope开头的链式查询方法,需在模型类定义并返回$query,调用时用->scope('name')或->scope(['name'=>val]),参数传递须匹配方法签名,不可在scope内执行select(),且受全局作用域影响。

怎么定义和调用 scope 方法
ThinkPHP 的模型查询范围(scope)本质是封装好的链式查询方法,必须定义在模型类里,且方法名以 scope 开头,比如 scopeHot、scopePublished。它接收两个参数:当前查询对象 $query 和可选的额外参数。
调用时不能直接当普通方法用,得走 where 那套链路——用 scope 作为查询条件传进去,比如 ->scope('hot') 或 ->scope(['hot', 'published'])。
- 必须返回
$query(否则链式中断) - 不能在 scope 里执行
select()或find(),只负责拼条件 - 多个 scope 同时用,顺序会影响 SQL 中
AND的组合逻辑,比如scopeHot先加时间过滤,再加状态过滤,和反过来结果可能不同
// 在 User.php 模型中
public function scopeHot($query, $days = 7)
{
$query->where('created_at', '>=', date('Y-m-d H:i:s', time() - $days * 86400));
}
// 调用
User::scope('hot', 30)->select();scope 参数传不进去?检查命名和调用方式
常见错误是写了 scopeHot($query, $limit),但调用时写成 ->scope('hot'),结果 $limit 是 null;或者误以为能用数组键名传参,比如 ->scope(['hot' => 10]),其实 ThinkPHP 不支持这种语法。
真正有效的传参只有两种:单个值(->scope('hot', 10))或带键的关联数组(->scope(['hot' => 10])),但后者要求 scope 方法签名必须用 ...$args 或明确接收 $params 数组。
立即学习“PHP免费学习笔记(深入)”;
-
->scope('hot', 10)→ 第二个参数会原样传给scopeHot的第二个形参 -
->scope(['hot' => 10])→scopeHot必须写成scopeHot($query, $params = [])才能取到$params['hot'] - 如果 scope 方法没声明第二个参数,又传了值,PHP 会报 Warning,但查询仍可能执行(只是参数被丢弃)
scope 和全局作用域(globalScope)混用时的优先级
全局作用域是模型自带的、每次查询都自动附加的条件,比如软删除的 delete_time IS NULL;而 scope 是显式调用才生效。两者叠加时,全局作用域的条件一定在最外层,scope 条件在其内侧生成,最终 SQL 是 WHERE (全局条件) AND (scope 条件)。
这意味着:如果全局作用域已加了 status = 1,你在 scope 里再写 $query->where('status', 2),就会变成 WHERE status = 1 AND status = 2,查不到任何数据。
- 调试时可用
User::scope('hot')->buildSql()看生成的 SQL,确认条件是否冲突 - 避免在 scope 里重复约束已被全局作用域控制的字段
- 若需临时绕过全局作用域,用
withoutGlobalScope(),但 scope 本身无法跳过它
为什么 scope 不生效?重点查这三处
scope 看似简单,但失效往往不是语法错,而是环境或调用路径问题。最容易忽略的是模型没正确加载、方法名拼错、或用了静态调用却忘了初始化模型上下文。
- 方法名必须是
scopeXxx,不能是hotScope或xxxScope,框架靠前缀识别 - 模型类必须继承
think\Model,且没有重写scope相关的父类方法 - 在控制器里用
UserModel::scope(...)没问题,但如果在 service 层用了 new User() 实例再调scope(),会报错——因为 scope 是查询构造器方法,只能在「查询构建器实例」上用,不能在模型实例上调
复杂点在于:scope 是查询构建阶段的语法糖,它不改变模型行为,也不影响事件或属性访问。一旦你开始混用 with、cache、useSoftDelete 这些特性,就得逐层验证 scope 是否还在查询链的正确位置上。


















