
laravel 报错 “recursion detected” 通常源于模型实例在序列化时意外引用自身,本文揭示其根本原因——控制器中重用循环变量名覆盖原始权限数据,并提供安全、可维护的修复方案。
laravel 报错 “recursion detected” 通常源于模型实例在序列化时意外引用自身,本文揭示其根本原因——控制器中重用循环变量名覆盖原始权限数据,并提供安全、可维护的修复方案。
该错误 Error encoding model [App\Models\RoleHasPermission] with ID [] to JSON: Recursion detected 并非数据库或模型定义问题,而是一个典型的变量作用域陷阱。关键问题出现在这段代码中:
foreach($permissions as $permission)
{
$permission = new RoleHasPermission; // ⚠️ 错误:用同名变量覆盖了原始 $permission(数组元素)
$permission->fkrole_id = $id;
$permission->fkpermission_id = $permission; // ❌ 此处 $permission 已是模型实例,而非整数ID!
$permission->save();
}当 foreach($permissions as $permission) 开始执行后,$permission 是当前循环中的权限 ID(如 5 或 12);但在循环体内,$permission = new RoleHasPermission 立即将其重新赋值为一个 Eloquent 模型对象。随后 $permission->fkpermission_id = $permission 实际上是将模型自身赋给了 fkpermission_id 属性——这导致模型属性间接持有了对自身的引用。当 Laravel 后续尝试将该模型转为 JSON(例如通过 API 响应、日志或调试输出),序列化器检测到无限嵌套引用链,从而抛出 Recursion detected 异常。
✅ 正确做法是严格区分数据变量与模型实例变量,使用语义清晰的命名:
public function storePermission(Request $request, $id)
{
$permissionIds = $request->input('permissions', []); // 显式类型提示 + 默认空数组
// 批量清理旧关联(建议使用事务保障一致性)
DB::transaction(function () use ($id, $permissionIds) {
RoleHasPermission::where('fkrole_id', $id)->delete();
foreach ($permissionIds as $permissionId) {
// ✅ 使用独立变量名,避免覆盖
$rolePermission = new RoleHasPermission();
$rolePermission->fkrole_id = $id;
$rolePermission->fkpermission_id = (int) $permissionId; // 强制类型转换,防注入
$rolePermission->save();
}
});
}? 进阶优化建议:
-
使用批量插入替代循环 save():大幅提升性能,尤其在权限数量较多时:
$data = array_map(fn($pid) => [ 'fkrole_id' => $id, 'fkpermission_id' => (int) $pid ], $permissionIds); RoleHasPermission::insert($data); -
启用 Eloquent 的强制类型转换(在模型中):
protected $casts = [ 'fkrole_id' => 'integer', 'fkpermission_id' => 'integer' ]; -
验证输入:在
Request中添加规则,例如'permissions.*' => 'required|integer|exists:permissions,id',提升健壮性。
总结:该错误本质是 PHP 变量复用引发的逻辑错位,而非 JSON 或模型配置缺陷。遵循“单一职责、命名隔离”原则,即可彻底规避此类递归陷阱,并让代码更清晰、更安全。


















