PHP中的对象复制及__clone() 函数

By | 2014/07/21

在PHP中, 对象间的赋值操作实际上是引用操作 (事实上,绝大部分的编程语言都是如此! 主要原因是内存及性能的问题) , 比如 :

class myclass {
public $data;
}
$obj1 = new myclass();
$obj1->data = "aaa";
$obj2 = $obj1;
$obj2->data ="bbb";     //$obj1->data的值也会变成"bbb"

因为$obj1和$obj2都是指向同一个内存区的引用,所以修改任何一个对象都会同时修改另外一个对象。

在有些时候,我们其实不希望这种reference式的赋值方式, 我们希望能完全复制一个对象,这是侯就需要用到 Php中的clone (对象复制)。

class myclass {
public $data;
}
$obj1 = new myclass();
$obj1->data ="aaa";
$obj2 = clone $obj1;
$obj2->data ="bbb";     // $obj1->data的值仍然为"aaa"

因为clone的方式实际上是对整个对象的内存区域进行了一次复制并用新的对象变量指向新的内存, 因此赋值后的对象和源对象相互之间是基本来说独立的。

什么? 基本独立?!这是什么意思? 因为PHP的object clone采用的是浅拷贝(shallow copy)的方法, 如果对象里的属性成员本身就是reference类型的,clone以后这些成员并没有被真正复制,仍然是引用的。 (事实上,其他大部分语言也是这样实现的, 如果你对C++的内存,拷贝,copy constructor等概念比较熟悉,就很容易理解这个概念), 下面是一个例子来说明:


class myClass{
public $data;
}

$sss ="aaa";
$obj1 = new myClass();
$obj1->data =&$sss;   //注意,这里是个reference!
$obj2 = clone $obj1;
$obj2->data="bbb";  //这时,$obj1->data的值变成了"bbb" 而不是"aaa"!

var_dump($obj1);
var_dump($obj2);

我们再举一个更实用的例子来说明一下PHP clone这种浅拷贝带来的后果:

class testClass
{
   public $str_data;
   public $obj_data;
}

$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));

$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;

$obj2 = clone $obj1;

var_dump($obj1);    // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);    // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"

$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));      //给$obj2->obj_date 的时间增加了10天

var_dump($obj1);     // str_data:"aaa"   obj_data:"2014-07-15 00:00:00"  !!!!
var_dump($obj2);     // str_data:"bbb"   obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj)  // 2014-07-15 00:00:00"

这一下可以更加清楚的看到问题了吧。 一般来讲,你用clone来复制对象,希望是把两个对象彻底分开,不希望他们之间有任何关联, 但由于clone的shallow copy的特性, 有时候会出现非你期望的结果,上面的例子中,
1) $obj1->obj_data =$dateTimeObj 这句话实际上是个引用类型的赋值. 还记得前面提到的PHP中对象直接的赋值是引用操作么?除非你用$obj1->obj_dat = clone $dataTimeObj!

2) $obj2 = clone $obj1 这句话生成了一个obj1对象的浅拷贝对象,并赋给obj2. 由于是浅拷贝,obj2中的obj_data也是对$dateTimeObj的引用!

3)$dateTimeObj, $obj1->obj_data, $obj2->obj_data 实际上是同一个内存区对象数据的引用,因此修改其中任何一个都会影响其他两个!

 

如何解决这个问题呢? 采用PHP中的 __clone方法 把浅拷贝转换为深拷贝(这个方法给C++中的copy constructor概念上有些相似,但执行流程并不一样)

class testClass
{
 public $str_data;
 public $obj_data;

 public function __clone() {
   $this->obj_data = clone $this->obj_data;
}

$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));

$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;

$obj2 = clone $obj1;
var_dump($obj1);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));

var_dump($obj1);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);  // str_data:"aaa"  obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj);  //"2014-07-05 00:00:00"

关于 __clone() , PHP官方的文档: Once the cloing is complete, if a __clone() method is defined, then the newly created object’s __clone() method will be called, to allow any necessary properties that need to be changed.

按照这个定义,事实上__clone方法可以做很多事情,但我目前能想到的就只有把 浅拷贝变成深拷贝 这个场景的应用了, 如果有其他用法,欢迎大家提出来。

 

5 thoughts on “PHP中的对象复制及__clone() 函数

  1. 我试了下,最后一段代码的出来的结果是
    object(test)#2 (2) { [“str_data”]=> string(2) “aa” [“obj_data”]=> object(DateTime)#1 (3) { [“date”]=> string(19) “2015-07-15 00:00:00” [“timezone_type”]=> int(3) [“timezone”]=> string(3) “UTC” } }

    object(test)#3 (2) { [“str_data”]=> string(3) “bbb” [“obj_data”]=> object(DateTime)#1 (3) { [“date”]=> string(19) “2015-07-15 00:00:00” [“timezone_type”]=> int(3) [“timezone”]=> string(3) “UTC” } }
    和您得出的并不一样= =!

    Reply
    1. 抱歉,我弄错了,是两个下划线“__clone()”,抱歉。

      Reply
  2. frost

    谢谢分享,今天第一次用clone 来分解不同的查询,因为我懒

    Reply

发表评论

电子邮件地址不会被公开。 必填项已用*标注

This site uses Akismet to reduce spam. Learn how your comment data is processed.