scope 是模型中以 scope 开头的 public 方法,仅拼接查询条件且必须操作 $query 对象;错误签名、返回数据或调用方式不当会导致静默失效。

Scope 不是配置项,也不是静态属性,而是模型里以 scope 开头的 public 方法;它不返回数据,只拼条件,且必须操作传入的 $query 对象。
scope 方法必须声明为 public 且接收 $query 参数
ThinkPHP 的 scope 本质是链式查询的“条件注入点”,不是普通工具函数。方法签名错一个字(比如写成 protected、漏掉 $query 参数、或加了 return []),就会导致静默失效或链路中断。
- 正确写法:
public function scopeHot($query) { $query->where('status', 1)->where('views', '>=', 1000); } - 错误写法:
protected function scopeHot() { return ['status' => 1]; }—— 权限不对 + 没参数 + 返回数组 - 错误写法:
public function scopeHot($query) { return $query->where(...)->select(); }—— 不能执行select(),只负责拼条件 - 调用时必须走模型实例或门面:
User::scope('hot')->select(),不能写User::scopeHot()或User::scope('hot', 1000)(除非方法定义了第二个参数)
带参 scope 的传参方式和常见错因
scope 支持传参,但 ThinkPHP 对参数传递有明确规则:单个值直接跟在 scope 名后,多个参数需封装进关联数组,且方法签名必须匹配。
-
User::scope('hot', 5000)->select()→ 要求方法定义为scopeHot($query, $min = 1000) -
User::scope(['hot' => 5000, 'top' => 10])->select()→ 要求方法定义为scopeHot($query, $params = []),再从$params['hot']取值 - 如果方法没声明第二个参数,却传了值,PHP 会发 Warning,但
$min始终为null,条件可能不生效 - 避免在 scope 内做类型强转,比如把字符串
'7'直接喂给subDays(),建议加is_numeric()或类型声明int $days
scope 组合调用与顺序敏感性
多个 scope 同时使用时,SQL 条件按调用顺序拼接,AND 关系下顺序一般不影响结果,但涉及 order、limit、group 等子句时,顺序决定最终行为。
-
User::scope('latest')->scope('active')->select():先排好序再过滤状态,ORDER BY在WHERE前,没问题 -
User::scope('active')->scope('latest')->select():先过滤再排序,更符合直觉,也更安全 -
User::scope('hot')->scope('top')->select()中,若scopeTop()包含limit(10),而scopeHot()返回 500 条,最终只取前 10 条 —— 这是预期行为,但容易被忽略 - 调试时用
User::scope('hot')->buildSql()看生成的 SQL,确认条件是否如你所想
scope 和全局作用域(globalScope)共存时的冲突风险
全局作用域(如软删除的 delete_time IS NULL)每次查询自动附加,scope 是显式调用。两者叠加时,全局条件永远在外层包裹 scope 条件,一旦逻辑冲突,查不到数据也不报错。
- 例如:全局 scope 加了
status = 1,你在scopeHot()里又写$query->where('status', 2),最终 SQL 是WHERE status = 1 AND status = 2 - 用
User::withoutGlobalScope()->scope('hot')->select()临时关闭全局作用域,验证是否是冲突导致空结果 - 不要在 scope 里重复写全局作用域已覆盖的字段,比如全局已限定
tenant_id = ?,就别在 scope 里再where('tenant_id', ...) - 关联查询(
with())不会继承 scope 条件,这是 ThinkPHP 的设计限制,需单独对关联模型调用 scope
真正难的不是写几个 scope 方法,而是搞清 $query 是谁传的、在哪被改、什么时候被最终执行 —— 它不保存状态,只是一次性的 Builder 实例。写完 scope 后,务必用 buildSql() 看 SQL,而不是靠“应该没错”去上线。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











