PHP:存在引用时克隆对象为何会破坏深克隆?
为什么持有对象内部属性的引用会导致深克隆失效?
复现代码
<?php class Person { public function __construct( public string $name, ) { } } class Country { public function __construct( public Person $person, ) { } public function __clone() { $this->person = clone $this->person; } } $usa = new Country(new Person('Arthur')); $blah = &$usa->person; // 这行导致深克隆失效 $italy = clone $usa; $italy->person->name = 'jack'; var_dump($usa); var_dump($italy);
输出结果
object(Country)#1 (1) { ["person"]=> &object(Person)#4 (1) { ["name"]=> string(4) "jack" } } object(Country)#3 (1) { ["person"]=> &object(Person)#4 (1) { ["name"]=> string(4) "jack" } }
核心原因:PHP的引用机制与克隆的交互
1. 引用改变了属性的存储方式
当你执行$blah = &$usa->person;时,$usa->person的底层存储逻辑被修改:
- 原本
$usa->person是直接持有Person对象的指针(zval容器类型为IS_OBJECT); - 创建引用后,
$usa->person变成了引用变量,和$blah共享同一个zval容器(类型变为IS_REFERENCE),这个容器才指向Person对象,此时该zval的引用计数为2。
2. 克隆时的引用传递逻辑
克隆$usa时,PHP的默认行为是:
- 创建新的Country对象
$italy; - 复制原对象的所有属性到新对象。对于引用类型的属性,PHP不会复制引用指向的对象,而是直接复制引用关系——也就是说,
$italy->person现在也和$usa->person、$blah共享同一个引用,指向原Person对象,zval的引用计数变为3。
3. __clone方法的赋值导致全引用同步
在Country::__clone方法中执行$this->person = clone $this->person;时:
$this->person是克隆后的$italy的属性,它是一个引用,指向原Person对象;clone $this->person会生成一个新的Person对象(#4);- 但因为
$this->person是引用,赋值操作会同步修改所有关联该引用的变量——也就是$usa->person和$blah都会被更新为指向这个新的Person对象#4; - 最终
$usa和$italy的person属性指向同一个对象,修改其中一个的属性自然会影响另一个。
4. unset引用后恢复正常的逻辑
当克隆前执行unset($blah):
$usa->person的zval引用计数降到1;- PHP会自动把
IS_REFERENCE类型的zval转换成普通的IS_OBJECT类型(没有其他变量共享引用,无需维持引用关系); - 此时克隆
$usa,$italy->person会复制原对象的普通对象指针(指向原Person对象); - 在
__clone方法中,将$italy->person赋值为克隆后的新Person对象,这样$usa和$italy的person属性分别指向不同对象,深克隆正常生效。
内容的提问来源于stack exchange,提问作者user3416510
相关产品推荐
相关产品推荐

