Hyperf的Database\Model与Laravel的Eloquent本质不同:前者常驻内存、协程共享,后者短生命周期、每次新建;直接复用Laravel写法会导致数据污染、连接泄漏、事务错乱,必须按协程安全原则重构模型生命周期、事务绑定和查询构造。

Hyperf 的 Hyperf\Database\Model 与 Laravel 的 Eloquent 表面相似,但底层行为完全不同:Eloquent 运行在短生命周期中,每次请求重建模型实例;Hyperf 模型常驻内存、协程共享,不加约束直接复用会导致数据污染、连接泄漏、事务错乱。平滑过渡的关键不是“改写法”,而是“重理解”。
Hyperf 中 Eloquent 模型不能直接复用 Laravel 写法
你把 Laravel 的 User::find(1) 复制进 Hyperf,代码能跑,但很快会出问题——比如并发请求下查到别人的用户数据、事务未提交却提前释放连接、static::$booted 在多次请求间残留状态。这不是 Bug,是模型生命周期错配。
- Hyperf 模型实例默认是单例(
singleton),Laravel 是每次 new 出来的新对象 - Laravel 的
DB::transaction()依赖 PHP-FPM 进程隔离,Hyperf 中需显式绑定到当前协程上下文 - 全局静态属性(如
Model::$with)在协程间不隔离,A 请求设了with(['profile']),B 请求可能意外继承 - Hyperf 的
Hyperf\Database\Model默认不开启自动事务,也不自动释放连接,需手动控制
协程安全的 Model 实例化必须显式声明
Hyperf 不会帮你“猜”哪个模型该复用、哪个该重建。所有涉及多请求共享的模型,必须通过容器明确生命周期策略。
- 需要每次请求新建:在
__construct()参数或@Inject注解中声明为transient,例如private User $user;+@Inject(transient=true) - 需要跨协程复用(极少见):使用
@Singleton,但必须确保内部无状态、不缓存查询结果、不持连接句柄 - 推荐做法:95% 场景下,模型应作为 transient 服务注入,避免任何静态缓存或长连接持有
- 不要在模型类里写
static $cache = []或protected $connection手动赋值——协程切换后这个连接可能已失效或被其他协程占用
事务必须绑定协程上下文,不能靠 try/catch 自动回滚
Laravel 的 DB::transaction() 在异常时自动 rollback,是因为它隐式依赖了请求生命周期结束时的资源清理;Hyperf 中没有这个兜底机制,漏写 rollback 就等于数据脏写。
- 正确写法是用
Hyperf\Database\Connection::transaction(),它内部已做协程上下文绑定 - 错误写法:
try { DB::beginTransaction(); ... DB::commit(); } catch { DB::rollback(); }—— 这里DB::是静态门面,不保证协程安全 - 必须用注入的连接实例:
$this->connection->transaction(function () { ... }); - 如果手动管理 begin/commit/rollback,务必在
finally块中调用$this->connection->release(),否则连接会被长期占用
Query Builder 的链式调用在协程中要防复用
DB::table('users')->where('id', 1) 看似无害,但它返回的是一个 builder 实例,若被多个协程共用(比如存在闭包缓存、静态变量保存),where 条件会叠加或覆盖。
- Builder 实例不是线程/协程安全的,不能跨请求复用
- 禁止将 builder 存入
static属性或全局变量 - 禁止在控制器方法外提前构造 builder 并传入协程:例如
private $builder = DB::table('users');是危险的 - 推荐模式:每个协程内按需创建 builder,用完即弃;高频场景可封装为工厂方法,每次返回新实例
最易被忽略的点是:Hyperf 的 ORM 不是 Laravel 的“升级版”,而是另一套运行契约。所谓平滑过渡,本质是放弃“写一次,到处跑”的幻想,接受协程模型对数据访问层的重新定义——模型即瞬态资源,事务即显式上下文,查询即一次性构造体。不踩坑的前提,是先承认它们根本就不是同一种东西。


















