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