參考鏈接:
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 } }