問題起因
前兩天有人在群組裡說了一個關於 new 和 stdClass 的問題,具體表現如下:
<?php $a = new stdClass; $b = new $a; var_dump($a, $b);
這段程式碼是可以正確運作的,並且 $a 和 $b 對象。即使在 new $a 之前為 $a 新增屬性並賦值,$b 也始終是一個的空物件。
所以問題就是:為什麼空對象還可以跟在 new 後面,stdClass 有什麼特別的地方嗎?
實際表現
其實主要稍加驗證就能知道,其實這和 stdClass 並沒有什麼關係,完全是 new 的行為決定的,例如在 psysh 上做一下簡單的測試:
這裡reee我是new 了一個 Reflection 類別的實例,和 stdClass 的表現沒有差別。當然也可以自訂一個類別:
>>> $a = new Reflection; => Reflection {#174} >>> $b = new $a; => Reflection {#177}
從這個例子中我們可以清楚的看到,改變 $a 的屬性對 $b 沒有任何影響(到這裡也可以順便思考一下 PHP 的一個關鍵字:clone)。
既然已經知道了表現,也可以得到結論:透過一個類別的物件 new 出一個新物件等同於 new 原物件的類別。
原因
那麼 PHP 是什麼樣的實現造成了這種表現呢?還是從源碼入手來解析這個問題。
其實從原始碼中,我們可以直奔 zend_vm_def.h 中找到答案,在關於 ZEND_FETCH_CLASS 這個opcode 的解釋中,我們可以看到以下內容:
>>> class Test { public $foo = 1; } => null >>> $a = new Test => Test {#178 +foo: 1, } >>> $a->foo = 2; => 2 >>> $b = new $a; => Test {#180 +foo: 1, }
去掉一些幹擾的上下文,上面的內容很清晰的呈現出一個解釋:如果取到的 class_name 是一個對象,則透過 Z_OBJCE_P的巨集找到它的類別。所以上面的表現解釋起來就很容易了。
這本身是一個很簡單的問題,不用往複雜了去想。如果想知道特定的 new 的實現,可以到 zend_compile.c 檔案中去查看 zend_compile_new 的實作。