HTML HTML5 PHP Mysql Linux 缓存技术 工具 资讯 读书 其他
当前位置: 资讯首页 » 全文内容

PHP对象复制 - clone

发布于: 2017-02-18 09:39:01 )

对象复制的由来


为什么对象会有“复制”这个概念,这与PHP5中对象的传值方式是密切相关的,让我们看看下面这段简单的代码:

//电视机类
class Television
{
    //屏幕高度
    protected $_screenLength = 300;

    //屏幕宽度
    protected $_screenHight = 200;

    //电视机外观颜色
    protected $_color = 'black';
   
    //返回电视外观颜色
    public function getColor()
    {
        return $this->_color;
    }

    //设置电视机外观颜色
    public function setColor($color)
    {
        $this->_color = (string)$color;
        return $this;
    }
}

$tv1 = new Television();
$tv2 = $tv1;
?>


这段代码定义了一个电视机的类 Television , $tv1为一个电视机的实例,然后我们按照普通的变量赋值方式将$tv1的值赋给$t2。那么现在我们拥有两台电视机$tv1和$tv2了,真的是这样的吗?我们来测试一下。

   //tv1的颜色是 black
   echo 'color of tv1 is: ' . $tv1->getColor();

   //tv2的颜色是
black
   echo 'color of tv2 is: ' . $tv2->getColor();

   //把tv2涂成白色
   $tv2->setColor('white');

   //tv2的颜色是 white
   echo 'color of tv2 is: ' . $tv2->getColor();
   
   ////tv1的颜色是 white
   echo 'color of tv1 is: ' . $tv1->getColor();
?>


首先我们看到tv1和tv2的颜色都是black,现在我们希望tv2换个颜色,所以我们将它的颜色设置成了white,我们再看看tv2的颜色,确实成为了white,似乎满足了我们的要求,可是并没有想象中的那么顺利,当我们接着看tv1的颜色的时候,我们发现tv1也由black边成了 white。

我们并没有重新设置tv1的颜色,为什么tv1会重black变成white呢?这是因为PHP5中对象的赋值和传值都是以“引用”的方式。

PHP5 使用了Zend引擎II,对象被储存于独立的结构Object Store中,而不像其它一般变量那样储存于Zval中(在PHP4中对象和一般变量一样存储于Zval)。在Zval中仅存储对象的指针而不是内容 (value)。当我们复制一个对象或者将一个对象当作参数传递给一个函数时,我们不需要复制数据。仅保持相同的对象指针并由另一个zval通知现在这个特定的对象指向的Object Store。由于对象本身位于Object Store,我们对它所作的任何改变将影响到所有持有该对象指针的zval结构----表现在程序中就是目标对象的任何改变都会影响到源对象。这使 PHP对象看起来就像总是通过引用(reference)来传递。所以以上的tv2和tv1其实是指向同一个电视机实例,我们对tv1或则tv2所做的操作其实都是针对这同一个实例。因此我们的“复制”失败了。看来直接变量赋值的方式并不能拷贝对象,为此 PHP5提供了一个专门用于复制对象的操作,也就是 clone ,这就是对象复制的由来。




用clone(克隆)来复制对象


我们现在使用PHP5的clone语言结构来复制对象,代码如下:

   $tv1 = new Television();
   $tv2 = clone $tv1;

   //tv1的颜色是 black
   echo 'color of tv1 is: ' . $tv1->getColor();
   
   //tv2的颜色是 black
   echo 'color of tv2 is: ' . $tv2->getColor();

   //把tv2换成涂成白色
   $tv2->setColor('white');

   //tv2的颜色是 white
   echo 'color of tv2 is: ' . $tv2->getColor();
   
   //tv1的颜色是 black
   echo 'color of tv1 is: ' . $tv1->getColor();
?>


这段代码的第2行,我们用clone关键字复制tv1,现在我们就拥有了一份真正的tv1的拷贝tv2,
我们还是按照之前的方法来检测复制是否成功。我们可以看到,我们将tv2的颜色换成了white,
tv1的颜色还是black,这样我们的复制操作就成功了。




__clone魔术方法


现在我们考虑到这样一个情况,每一台电视机应该都有自己的编号,这个编号如同我们的身份证号码一样应该是唯一的,所以当我们在复制一台电视机的时候,我们不希望这个编号也被复制过来,以免造成一些麻烦。
我们想到的一个策略是将赋值出来的电视机的编号清空,然后再按照需求来重新分配编号。
那么__clone魔术方法就是专门用来解决这样的问题,__clone魔术方法会在对象被复制( 也就是clone操作)的时候被触发。我们修改了电视机类Television的代码,添加了编号属性和__clone方法,代码如下:

//电视机类
class Television
{
    //电视机编号
    protected $_identity = 0;

    //屏幕高度
    protected $_screenLength = 300;

    //屏幕宽度
    protected $_screenHight = 200;

    //电视机外观颜色
    protected $_color = 'black';

    //返回电视外观颜色
    public function getColor()
    {
        return $this->_color;
    }

    //设置电视机外观颜色
    public function setColor($color)
    {
        $this->_color = (string)$color;
        return $this;
    }

    //返回电视机编号
    public function getIdentity()
    {
        return $this->_identity;
    }

    //设置电视机编号
    public function setIdentity($id)
    {
        $this->_identity = (int)$id;
        return $this;
    }

    public function __clone()
    {
        $this->setIdentity(0);
    }
}
?>

下面我们来复制这样的一个电视机对象。
   $tv1 = new Television();
   $tv1->setIdentity('111111');
   echo 'id of tv1 is ' . $tv1->getIdentity();//111111
   echo '
';

   $tv2 = clone $tv1;
   echo 'id of tv2 is ' . $tv2->getIdentity();//0
?>


我们生产了一台电视机tv1 , 并且设置它的编号为111111,然后我们用clone将tv1复制得到了tv2,这个时候__clone魔术方法被触发,此方法将直接作用与复制得到的对象tv2,我们在__clone方法中调用了setIdentity成员方法将tv2的_identity属性清空,以便我们后面对它进行重新编号。由此我们可以看到__clone魔术方法能让我们非常方便的在clone对象的时候做一些附加的操作。

总结:

不同的对象复制方式有着不同的效果,我们应该根据具体应用需求来考虑使用哪种方式以及如何改进复制方式。PHP5的面向对象特性和JAVA比较接近,相信我们可以从JAVA中借鉴很多宝贵的经验。
To Top