Laravel Observer updated事件触发循环 引发SQLSTATE[22001]字段过长错误
触发循环的核心逻辑
无限递归的链路非常清晰:
- 控制器内首次执行
$species->save(),完成数据库写入后,自动触发Species模型的updated事件 - 进入观察者的
updated方法后,代码修改name字段后再次调用$species->save() - 这次写入会再次触发
updated事件,重复执行「修改name -> 保存」的流程,形成无限循环 - 循环持续到
name字段的字符串长度超过数据库定义的列上限,抛出SQLSTATE[22001]: 字符串数据,右截断:1406 列存储数据过长错误
可直接落地的修复方案
直接打断循环链路
把观察者方法内的普通save()替换为saveQuietly(),该方法执行数据库写入时不会触发任何模型事件,从根源上切断递归触发的路径。同时要增加重复拼接校验,哪怕循环被打断,也避免多次触发更新时反复给name叠加ID后缀。
修正后的观察者代码示例:public function updated(Species $species) { // 校验是否已经追加过对应ID,避免重复拼接 if (!str_contains($species->name, " - {$species->id}")) { $species->name = "{$species->name} - {$species->id}"; // 静默写入,不触发后续模型事件 $species->saveQuietly(); } }从流程设计上规避重复触发
完全不需要依赖观察者做这个逻辑的话,直接在控制器的更新流程里拼接带ID的名称,在首次save之前完成字段赋值,彻底绕开事件触发的问题:public function update(Request $request, Species $species) { $species->name = $request->name . ' - ' . $species->id; $species->sort = $request->sort; $species->save(); $updatedSpecies = new SpeciesResource($species); return response()->json(compact('updatedSpecies'), 200); }适配观察者的正确钩子时机
如果坚持要用观察者实现这个逻辑,不要监听updated(更新完成后触发)事件,改成监听saving(执行数据库写入前触发)事件,整个流程只执行一次数据库写入,完全不会出现循环:/** * 处理species保存前的逻辑 * @param \App\Species $species * @return void */ public function saving(Species $species) { // 避免重复拼接ID后缀 if (!str_contains($species->name, " - {$species->id}")) { $species->name = "{$species->name} - {$species->id}"; } }
避坑提醒
- 只要是在
updated、saved这类「数据库写入完成后」触发的模型钩子里,直接调用普通save()方法、且没有做事件拦截,必然会触发同类型的下一轮钩子,形成无限递归 - 提前给数据库中
name字段的长度预留冗余空间,避免后续逻辑调整时偶发的字符串截断错误
内容的提问来源于stack exchange,提问作者cyber-leech
相关产品推荐
相关产品推荐

