php 的 clone 运算符默认是浅拷贝:创建新对象但引用类型属性仍共享内存,修改嵌套对象会导致原对象同步变化;需通过 __clone() 方法手动实现深拷贝逻辑。

clone 是深拷贝还是浅拷贝?
PHP 的 clone 运算符默认只做**浅拷贝**:它会创建新对象,但对象内部的引用类型属性(比如另一个对象、数组里的对象、资源等)仍指向原对象的同一份内存。
这意味着修改克隆体里的某个嵌套对象,原对象可能同步变化——这不是 bug,是设计如此。
- 如果属性是标量(
int、string)、不可变对象(如DateTimeImmutable),或 null,浅拷贝表现安全 - 如果属性是普通对象(如
StdClass或自定义类实例)、引用数组、resource,就得手动处理 - 想实现真正“隔离”的副本,必须在
__clone()方法里重写关键属性的复制逻辑
什么时候必须写 __clone() 方法?
当你克隆的对象里包含需要独立副本的引用型数据时,__clone() 就不是可选项,而是必选项。常见场景包括:持有数据库连接、缓存实例、配置对象、或任何内部状态不能共享的依赖。
不写 __clone() 而直接 clone,容易导致两个对象意外共享状态,引发竞态或脏数据。
立即学习“PHP免费学习笔记(深入)”;
-
__clone()是魔术方法,仅在clone执行后自动调用一次,不能手动触发 - 方法体内需显式重新赋值所有需深拷贝的属性,例如:
$this->config = clone $this->config; - 注意递归克隆风险:如果属性本身也含需克隆的引用,要逐层判断,避免无限循环
clone 和 serialize/unserialize 有什么区别?
两者都能得到独立副本,但机制和代价完全不同:clone 是内存级复制,快但可控性低;unserialize(serialize($obj)) 是序列化反序列化,本质是深拷贝,但会绕过构造函数、忽略 __clone(),且性能差、不支持闭包和资源类型。
-
clone保留原始对象的类身份、private/protected 属性可见性、以及所有运行时状态(除你手动改的) -
serialize/unserialize会丢失资源(如mysqli连接)、跳过未声明的动态属性、且要求类可被序列化(无不可序列化成员) - 不要用
serialize替代__clone()—— 它解决的是不同问题,还可能引入静默失败
克隆失败的常见错误现象
最典型的不是报错,而是行为诡异:比如克隆后修改子对象,原对象跟着变;或者克隆完发现某些属性变成 null 或空数组——这往往是因为 __clone() 里漏写了某一行赋值,或误用了 parent::__clone()(PHP 不自动调用父类 __clone())。
- 错误信息不会出现,PHP 不校验
__clone()是否完整,全靠你自查 - 继承链中,子类
__clone()必须显式调用parent::__clone(),否则父类引用属性不会被重置 - 使用
final类或不可克隆类(如Closure)时,clone会抛出Fatal error: Uncaught Error: Trying to clone an uncloneable object
__clone() 时,最容易被忽略的是“谁该被克隆、谁不该”——比如日志记录器、事件分发器这类服务对象,通常应保持单例引用,而非每次克隆都复制一份。克隆不是万能备份工具,得看对象语义。










