PHP对象赋值默认是引用,$new = $old仅为别名,不创建新实例;修改任一变量均影响对方,必须用clone显式创建独立对象。

不用 clone 就得不到独立对象实例——PHP 对象赋值默认是引用,= 只是多绑一个变量名到同一块内存,改谁都一样。
为什么直接 $new = $old 不行?
因为这不是复制,是 alias(别名)。两个变量指向同一个对象实体,属性修改、方法调用、状态变更全部共享。常见错误现象包括:
- 在循环中
$item = $array[$i]后改$item->status,结果原数组里对应项也变了 - 传给函数的参数被函数内部修改,外部对象意外“被污染”
- 缓存、连接池、计数器等有状态对象被多个逻辑共用,引发竞态或数据错乱
唯一能打破这个引用链的语法就是 clone 关键字——它强制分配新内存、复制所有属性值,并触发 __clone()(如果定义了)。
clone 默认是浅拷贝,嵌套对象仍共享引用
执行 $copy = clone $obj 后,$obj 和 $copy 是两个不同对象,但它们内部的引用型属性(如另一个对象、资源、引用数组)仍指向同一份底层数据。
立即学习“PHP免费学习笔记(深入)”;
- 标量(
int、string、null)、不可变对象(DateTimeImmutable)安全 - 普通对象(
StdClass、自定义类实例)、ArrayObject、mysqli连接句柄等不安全 - 典型表现:改
$copy->child->name,$obj->child->name也跟着变
这不是 bug,是 PHP 的设计选择——克隆开销可控,深拷贝必须由开发者显式声明意图。
怎么写可靠的 __clone() 实现深拷贝?
__clone() 是唯一能干预克隆过程的入口,但它不会自动调用父类版本,也不处理未声明的属性。关键实操点:
- 必须手动
clone所有需要隔离的引用属性:$this->cache = clone $this->cache; - 数组里混对象?得遍历判断:
foreach ($this->items as $k => $v) { if (is_object($v)) $this->items[$k] = clone $v; } - 继承场景下,子类
__clone()必须显式调用parent::__clone();,否则父类里的引用属性不会重置 - 禁止在
__clone()中调用__construct()或new static(),PHP 会报Fatal error: Cannot call constructor - 资源(
fopen()返回值)、闭包、PDOStatement 等不可克隆类型,尝试clone会直接 fatal
别用 serialize() / unserialize() 替代 clone
虽然 unserialize(serialize($obj)) 看似能“深拷贝”,但它和 clone 解决的是不同问题:
- 它绕过
__construct()和__clone(),丢失运行时状态(如未序列化的资源、动态属性) - 性能差一个数量级,尤其对大对象或含大量字符串的数据
- 无法处理
resource、Closure、部分 SPL 对象,反序列化会失败或静默丢弃 - 不保留原始对象的 private/protected 属性可见性规则(取决于
__sleep()实现)
真正需要深拷贝时,应聚焦在 __clone() 内部做精准控制——只克隆那些确实需要隔离的引用,而不是无差别序列化整个对象图。
最常被忽略的不是语法怎么写,而是克隆后没验证内部对象是否真的隔离。尤其在单测里看不出问题,上线后高并发下多个克隆体争抢同一缓存实例或数据库连接,错误才集中爆发。



















