replicate() 是唯一安全、可预期的模型复制入口,因 clone 仅浅拷贝导致共享数据库连接、缓存、事件监听器等引发状态错乱;replicate() 新建实例并重置关键状态,但不处理关联、访问器、附件等,需手动深克隆与事务保障。

replicate() 是唯一安全、可预期的模型复制入口,clone 关键字直接用在 Eloquent 实例上会报 Call to undefined method 错误,且极易引发内存泄漏或状态错乱。
为什么不能用 PHP 的 clone 关键字
Eloquent 模型内部持有数据库连接、关系缓存、事件监听器、属性快照($original、$changes)等大量引用对象。clone 只做浅拷贝,无法切断这些关联,导致两个变量实际共享同一底层状态。常见现象包括:
- 修改新模型的某个字段,原模型对应字段也跟着变
- 调用
save()后触发了原模型的saving事件两次 - 内存占用异常升高,GC 难以回收
replicate() 做了什么、没做什么
replicate() 是 Laravel 官方支持的复制方法,它会:
- 新建一个模型实例,
$exists = false,不绑定任何数据库记录 - 复制所有当前可见属性值(即
$model->getAttributes()中的键值) - 清空主键(
id)、时间戳(created_at/updated_at)、软删除字段(deleted_at) - 重置内部状态:
$wasRecentlyCreated = false,$timestamps = true仍生效
但它不会:
- 复制关联数据(
hasMany、belongsToMany等) - 复制访问器(
getXXXAttribute)计算出的值 - 复制
$appends字段或动态属性 - 处理唯一约束字段(如
slug、email),需手动改写
带关联的深克隆必须手动处理
没有“一键深克隆”,Eloquent 不提供自动递归复制关系的能力。多对多和一对多必须分开处理:
- 对
belongsToMany:先save()新模型,再用sync()或attach()绑定原关联 ID 列表 - 对
hasMany:遍历原集合,逐条replicate()并显式设置外键指向新模型 ID - 务必预加载关联(
with(['tags', 'comments'])),否则 N+1 查询会导致性能骤降甚至超时 - 若涉及文件附件(如
media关系),replicate()不会复制物理文件,需额外调用存储驱动复制
事务、事件与字段填充的坑
复制操作不是原子的,默认不包裹在事务中。如果中途失败(如关联保存出错),可能留下半成品数据:
- 用
DB::transaction()包裹整个克隆流程,确保一致性 - 注意模型事件:默认会触发
creating和saving,但不会触发created直到真正写入成功 -
$fillable和$casts会影响复制结果——未列入$fillable的字段即使有值也不会被replicate()保留 - 软删除记录克隆后,
deleted_at会被复制为非 null 值,新模型一入库就处于“已删除”状态,需手动设为null
真正容易被忽略的是:复制后的模型不继承原始模型的查询上下文(比如 where 条件、select 字段、with 加载状态),也不携带原始 $original 快照;它只是一张干净的属性白纸——这既是安全点,也是你需要自己补全逻辑的地方。


















