參考連結:
1、php.net官網文件 - 物件複製
什麼時候用到?摘自php.net:
在多數情況下,我們並不需要完全複製一個物件來獲得其中屬性。但有一個情況下確實需要:如果你有一個 GTK 視窗對象,該對象持有視窗相關的資源。你可能會想複製一個新的窗口,保持所有屬性與原來的窗口相同,但必須是一個新的對象(因為如果不是新的對象,那麼一個窗口中的改變就會影響到另一個窗口)。還有一種情況:如果物件A 中保存著物件B 的引用,當你複製物件A 時,你想其中使用的物件不再是物件B 而是B 的一個副本,那麼你必須得到物件A 的副本。
嘗試使用最簡單的「=」
首先要明確的是:php的物件是以一個標識符來儲存的,所以對物件的直接「賦值」行為相當於「傳引用」
<?php function dump($var){ var_dump($var); echo "<br/>"; } class A{ private $a; protected $b; public $c; public function d(){ echo "A -> d"; } } $a1 = new A(); $a2 = $a1; $a3 = new A(); dump($a1); dump($a2); dump($a3);
輸出的結果是:
object(A)#1 (3) { ["a":"A":private]=> NULL ["b":protected]=> NULL ["c"]=> NULL } object(A)#1 (3) { ["a":"A":private]=> NULL ["b":protected]=> NULL ["c"]=> NULL } object(A)#2 (3) { ["a":"A":private]=> NULL ["b":protected]=> NULL ["c"]=> NULL }
其中可以注意到,作為對象標識符的#n,顯示$a1和$a2其實是指向同一個對象,而$a3是另一個對象
所以,如果需要拷貝一個相同且全新的對象,不能直接通過=來複製,否則改變了$a1->a就相當於修改了$a2->a。
淺拷貝
PHP5中,類別中有個魔術方法__clone(),在配合clone關鍵字和物件使用的時候,會自動呼叫(如果沒有明確定義,則呼叫空的方法)。
clone關鍵字的作用是,複製某一個對象形成一個對象的“淺拷貝”,然後賦值給新的對象,此時對象標識符不同了!
<?php function dump($var){ var_dump($var); echo "<br/>"; } class B{ public $d; } class A{ public $a; public $b; public function d(){ echo "A -> d"; } } $a1 = new A(); $a1->a = 123; // 这里对象属性的值是一个对象示例,其实就是存储了对象标识符。使用clone关键字生成的拷贝中的b属性仍然指向旧对象的b属性指向的对象,这是"浅拷贝"出现的问题。如果需要指向一个新的对象,必须"深拷贝" $a1->b = new B(); // PHP 5 only $a2 = clone $a1; dump($a1); dump($a2);
輸出的結果是:
object(A)#1 (2) { ["a"]=> int(123) ["b"]=> object(B)#2 (1) { ["d"]=> NULL } } object(A)#3 (2) { ["a"]=> int(123) ["b"]=> object(B)#2 (1) { ["d"]=> NULL } }
可以看到,$a1和$a2明顯是兩個不同的物件(物件識別碼不同了)。但需要留意的一點是,"b"指向的物件識別碼都是#2,證明這兩個物件是相同的,這就是「淺拷貝」的「缺陷」——但有時這兩個物件確實需要相同,所以PHP的clone預設是「淺拷貝」。
為什麼叫淺拷貝(shallow copy)?
因為在複製的時候,所有的屬性都是“值傳遞”的,而上面的b屬性存儲的是對象標識符,所以相當於做了“引用傳遞”,這並不是完全的拷貝,所以稱為「淺拷貝」。
深拷貝
上面講到,使用clone關鍵字的時候,會自動呼叫舊物件的__clone()方法(然後傳回拷貝的物件),所以只需要在對應的類別中重寫__clone()方法,使返回的物件中的「引用傳遞」的屬性指向另一個新的物件。以下是例子(可以比較「淺拷貝」的例子,其實多了重寫__clone()的步驟):
<?php function dump($var){ var_dump($var); echo "<br/>"; } class B{ public $d; } class A{ public $a; public $b; public function d(){ echo "A -> d"; } public function __clone(){ // clone自己 $this->b = clone $this->b; } } $a1 = new A(); $a1->a = 123; // 这里对象属性的值是一个对象示例,其实就是存储了对象标识符。使用clone关键字生成的拷贝中的b属性仍然指向旧对象的b属性指向的对象,这是"浅拷贝"出现的问题。如果需要指向一个新的对象,必须"深拷贝" $a1->b = new B(); // PHP 5 only $a2 = clone $a1; dump($a1); dump($a2);
結果就不同了,注意b屬性的物件識別碼:
object(A)#1 (2) { ["a"]=> int(123) ["b"]=> object(B)#2 (1) { ["d"]=> NULL } } object(A)#3 (2) { ["a"]=> int(123) ["b"]=> object(B)#4 (1) { ["d"]=> NULL } }