hyperf 模型 scope 不支持运行时切换数据库连接,必须在调用 scope 前通过 on()、静态方法封装或重写 newquery() 显式指定连接;scope 内调用 on() 或 db::connection() 均无效且危险。

Hyperf 模型的 scope 作用域本身不直接支持指定数据库连接,因为作用域方法运行在查询构造器(Builder)层面,而连接是在 Query\Builder 实例创建时由模型或查询入口决定的。要让 scope 生效于特定数据库连接,需在调用 scope 前就切换连接上下文。
scope 内不能动态切库,必须靠外部驱动
作用域方法(如 scopeActive)接收的是已初始化的 $query 实例,它已绑定所属模型的默认连接。你无法在 scope 内部调用 $query->connection('xxx') 或类似方式切换——Hyperf 的 Builder 不提供运行时换连接的公开接口,强行反射操作会破坏协程安全且不可维护。
- scope 是“条件追加器”,不是“连接控制器”
- 所有连接信息来自模型类的
$connection属性或on('xxx')显式声明 - 试图在 scope 里写
$query->getConnection()->reconnect()属于误用,易引发连接泄漏或跨协程污染
正确做法:在调用链起点指定连接
想让带 scope 的查询走指定库,应在发起查询时就明确连接,再链式调用 scope:
-
方式一:用
on()指定连接后调用 scopeUser::on('tenant_db')->scopeActive()->get(); -
方式二:通过模型静态方法封装连接 + scope 组合
在User模型中添加:public static function tenantQuery(): self<br>{<br> return (new static())->on('tenant_db');<br>}
然后使用:User::tenantQuery()->scopeActive()->get(); -
方式三:多租户场景下统一由 TenantContext 驱动
结合中间件自动设置当前租户连接,在模型基类重写newQuery():protected function newQuery() {<br> $query = parent::newQuery();<br> $tenantId = TenantContext::id();<br> if ($tenantId) {<br> $query->on("tenant_{$tenantId}");<br> }<br> return $query;<br>}
此时User::scopeActive()->get()自动走对应租户库
避免常见错误写法
以下写法均无效或危险:
-
public function scopeActive($query) { $query->on('other_db')->where('status', 'active'); }→on()在 Builder 上无返回值,且不生效 -
DB::connection('other_db')->table('users')->scopeActive()→scopeActive是模型方法,DB::table()返回的是原生查询器,不识别 scope - 在 scope 中调用
DB::connection('xxx')并尝试 setQuery → 破坏原有 builder 关系,后续链式调用可能崩溃
高频场景建议:封装带连接的查询类
若多个模型需共用同一套连接 + scope 组合,推荐用 trait 封装可复用逻辑:
- 定义
TenantScopedQuerytrait,提供tenantQuery(string $type)方法 - 每个模型 use 该 trait,并在 controller 中统一调用:
User::tenantQuery('read')->scopeActive()->latest()->get() - 连接名可按读写分离策略动态生成,如
"tenant_{$id}_read"











