Hyperf模型的scopes是封装WHERE条件的便捷方式,分本地作用域(scopeXxx方法,链式调用)和全局作用域(自动注入通用条件,如tenant_id过滤),二者均用于复用查询逻辑,提升可读性与可维护性。

Hyperf 模型的 scopes(作用域查询)是封装常用 WHERE 条件的便捷方式,让查询逻辑复用、可读性高、不易出错。它不是“写死在控制器里”的重复代码,而是定义在模型或 trait 中、通过方法链调用的动态条件。
本地作用域(Local Scopes)怎么定义和调用
本地作用域是模型或 trait 中以 scope 开头的公共方法,第一个参数固定为 $query(即 QueryBuilder 实例),后续参数接收业务值。
- 必须是 public 方法,命名格式为
scopeXxx(如scopeType),调用时用xxx()(自动去掉scope前缀) - 方法内直接操作
$query->where(...),不 return,框架自动返回 builder 实例 - 支持链式调用,比如
User::query()->type('admin')->sex('male')->get()
示例(在 trait 中定义):
<?phptrait LinkQuery {
public function scopeType($query, $typeVal) {
$query->where('type', $typeVal);
}
public function scopeSex($query, $sexVal) {
$query->where('sex', $sexVal);
}
}
全局作用域(Global Scopes)自动注入通用条件
全局作用域适用于所有该模型的查询都需强制附加的条件,比如多租户系统的 tenant_id 过滤,或软删除字段 deleted_at IS NULL。
- 推荐实现
Scope接口(如TenantScope),在apply()方法中添加 where - 在模型的
boot()方法中注册:static::addGlobalScope(new TenantScope()); - 也可用闭包方式快速注册:
static::addGlobalScope('tenant', fn($builder) => $builder->where('tenant_id', get_tenant_id())); - 临时取消全局作用域:用
withoutGlobalScopes()或withoutGlobalScope(TenantScope::class)
静态查询方法 vs scopes:什么时候选哪个
两者都能封装查询逻辑,但语义和扩展性不同:
- scopes 是 builder 级别增强,天然支持链式、可组合、可被其他 scope 复用;适合「条件片段」
-
静态方法(如
public static function latestUsers())更适合封装完整查询流程,比如带orderBy、limit、with关联等复合逻辑 - 注意:scope 方法不能直接调用
get()或first(),否则会中断链式;如需立即执行,应另写静态方法
调试与注意事项
scopes 看似简单,但容易踩坑:
- 确保方法名严格遵循
scopeXxx规则,否则调用时找不到(Hyperf 不报错,只是静默忽略) - 不要在 scope 中做 DB 查询、Redis 调用等耗时操作——它是构建 SQL 的阶段,不是执行阶段
- 多个 scope 同时使用时,where 条件按调用顺序叠加,无隐式优先级;复杂逻辑建议抽成独立 scope 而非嵌套 if
- 测试 scope 是否生效?可用
toSql()查看生成的 SQL:User::query()->type('vip')->toSql()










