PHP的clone默认是浅拷贝:标量和数组值被复制,对象、资源等引用类型仍共享内存地址;要实现深拷贝,必须在__clone()中手动克隆嵌套对象或用serialize/unserialize兜底。

PHP 的 clone 默认就是浅拷贝,不写 __clone() 也一样;想深拷贝,必须手动处理对象属性,否则改副本会悄悄改原对象。
为什么 clone $obj 后改子对象还影响原对象
因为 PHP 默认只复制属性的值:标量(string、int)、数组是值拷贝;但对象、资源、闭包这些仍是引用拷贝——$copy->user 和 $original->user 指向同一内存地址。
- 常见错误现象:
Call to private __clone method(父类声明了private __clone(),子类重写也无效) -
Clone of non-object(试图克隆null或已关闭的资源) - 没定义
__clone()→ 完全走默认逻辑,毫无深拷贝效果 - 写了
__clone()但漏掉某个对象属性 → 该属性仍共享引用
__clone() 方法什么时候真正起作用
仅在显式使用 clone 关键字时触发,赋值($b = $a)、函数传参、json_encode()、serialize() 都不会调用它。
- 它不接受任何参数,不能是
private或protected,必须为public - 不会调用
__construct(),所以构造时的 DB 查询、初始化逻辑全部跳过 - 如果类里有
private __clone(),哪怕子类重写public __clone(),也会报错——访问控制在父类就卡死了 - PHP 8.2+ 对部分内部类(如
SplFixedArray)克隆更严格,直接抛Cannot clone object
怎么安全实现深拷贝:手动 vs 序列化
手动处理可控但易漏,序列化兜底方便但有硬限制。选哪个取决于你的对象结构和运行环境。
立即学习“PHP免费学习笔记(深入)”;
- 手动深拷贝:在
__clone()中逐个判断并克隆对象属性public function __clone() { if ($this->address instanceof Address) { $this->address = clone $this->address; } if (is_object($this->config)) { $this->config = clone $this->config; } } - 序列化兜底:
unserialize(serialize($obj))自动递归,但要求所有嵌套对象可序列化
→ 不支持Closure、资源句柄、PDO 实例、含__sleep()报错的类
→ 不触发__clone(),所以重置 ID、清缓存等自定义逻辑不会执行 - 性能差异明显:
serialize/unserialize比手动clone慢 3–5 倍,高频调用需压测
最容易被忽略的设计前提:原型对象本身得干净
深拷贝解决不了设计问题。如果原型对象里存了请求上下文、临时 token、未 reset 的计数器,克隆出来照样带毒——clone 只复制状态,不净化语义。
真正该花时间的地方,是让原型对象提前进入“稳定可复用态”:配置加载完毕、连接池就绪、ID 为空、缓存已预热。否则,再完美的深拷贝也只是把脏数据复制得更彻底。



















