标题:关于PHP中对象复制的那点事儿

文章目录
    分类:PHP 标签:PHP基础

    关于PHP中对象复制的那点事儿

    我们已经在PHP设计模式之原型模式中讨论过关于PHP中对象复制的问题,这次就当做是一次复习。


    原型模式可以看作是对象复制中的一个重要内容。在学习原型模式时,我们了解到对象中的引用变量,也就是变量也是一个对象时,直接复制这个对象会导致其中的引用变量还是指向同一个对象。是不是有点绕,我们还是用例子来说明:

    // clone方法
    class testA{
        public $testValue;
    }
    class A
    {
        public static $reference = 0;
        public $instanceReference = 0;
        public $t;
    
        public function __construct()
        {
            $this->instanceReference = ++self::$reference;
            $this->t = new testA();
    
        }
    
        public function __clone()
        {
            $this->instanceReference = ++self::$reference;
            $this->t = new testA();
        }
    }
    
    $a1 = new A();
    $a2 = new A();
    $a11 = clone $a1;
    $a22 = $a2;
    
    var_dump($a11); // $instanceReference, 3
    var_dump($a22); // $instanceReference, 2
    
    $a1->t->testValue = '现在是a1';
    echo $a11->t->testValue, PHP_EOL; // ''
    
    
    $a2->t->testValue = '现在是a2';
    echo $a22->t->testValue, PHP_EOL; // 现在是a2
    $a22->t->testValue = '现在是a22';
    echo $a2->t->testValue, PHP_EOL; // 现在是a22
    
    // 使用clone后
    $a22 = clone $a2;
    var_dump($a22); // $instanceReference, 4
    
    $a2->t->testValue = '现在是a2';
    echo $a22->t->testValue, PHP_EOL; // NULL
    $a22->t->testValue = '现在是a22';
    echo $a2->t->testValue, PHP_EOL; // 现在是a2

    首先,通过变量的变化,我们可以看出使用clone关键字的对象复制会调用__clone()方法。这个魔术方法正在原型模式的核心所在。在这个方法中,我们可以重新实例化或者定义对象中的引用成员。通过clone,我们让$t变量重新实例化,从而让$t成为了新的对象,从而避免引用带来的问题。


    在对象的复制中,我们需要特别注意的递归引用的问题。也就是对象内部引用了自身,将会导致来回的重复引用形成递归死循环。

    // 循环引用问题
    class B
    {
        public $that;
    
        function __clone()
        {
            // Segmentation fault: 11
            $this->that = clone $this->that;
            // $this->that = unserialize(serialize($this->that));
            // object(B)#6 (1) {
            //     ["that"]=>
            //     object(B)#7 (1) {
            //       ["that"]=>
            //       object(B)#8 (1) {
            //         ["that"]=>
            //         *RECURSION*  无限递归
            //       }
            //     }
            //   }
        }
    }
    
    $b1 = new B();
    $b2 = new B();
    $b1->that = $b2;
    $b2->that = $b1;
    
    $b3 = clone $b1;
    
    var_dump($b3);

    B类中的that指向自身的实例,两个对象相互指向后再进行复制,就会出现这种死循环的情况。使用序列化和反序列化输出后,我们会看到RECURSION的引用提示。这就是形成了递归的死循环。这种情况一定要极力避免。


    上述例子中,我们使用了序列化和反序列化这一招来解决引用问题。对象复制的对象变量来说(对象变量里面还有更多层次的引用变量),这种方式能够一次性地在最顶层的对象__clone()方法中解决引用问题。


    测试代码:

    https://github.com/zhangyue0503/dev-blog/blob/master/php/202001/source/%E5%85%B3%E4%BA%8EPHP%E4%B8%AD%E5%AF%B9%E8%B1%A1%E5%A4%8D%E5%88%B6%E7%9A%84%E9%82%A3%E7%82%B9%E4%BA%8B%E5%84%BF.php


    参考文档:

    https://www.php.net/manual/zh/language.oop5.cloning.php

    视频链接

    B站视频地址:https://www.bilibili.com/video/BV1hT4y1c7i8

    微信视频地址:http://mp.weixin.qq.com/s?__biz=MzIxODQyNTU1MA==&mid=2247484865&idx=1&sn=78b93ebc6073041e45f71b30a8d236ba&chksm=97ebf460a09c7d76502f226f669dfa11b08537a05dee6b0475d74a4619f864978fd59843e187&scene=27#wechat_redirect

    微信文章地址:http://mp.weixin.qq.com/s?__biz=MzIxODQyNTU1MA==&mid=2247484418&idx=1&sn=322baa34017ce859fb6396e566ea7c3b&chksm=97ebf5a3a09c7cb53953049fe3e51946e9443412e7bce5a792567f0fd26a42fe79ff871ec46f&scene=27#wechat_redirect

    搜索
    关注