You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 02:59:57