轉載請註明文章出處:tlanyan.me/dynamic-new…
問題
前幾天有人在PHP的QQ群裡問生成物件的問題:
use A\B;
$b = new B(); // 正確
$str = "B";
$b = new $str(); // 錯誤,提示:類"B"未找到
複製程式碼
類似問題五六年前碰到過,因此印象深刻。熱心提示要用 "完全限定類名" 形式,可惜連說兩遍,提問題的人都沒理解我說的(或者認為我的回覆與其問題無關):
不得已下,寫下示範程式碼並 @ 提問題的人,終於讓其明白:
原理
問題解決了,背後的原理是什麼?
從人的角度看,程式碼意圖非常明顯:動態生成類B
的例項。但從執行引擎的角度,完全是另外一回事。其實new $classname()
背後的運作行為類似於:
// 虛擬碼
if (class_exists($str)) {
$b = new $str();
return $b;
}
throw ClassNotFoundException;
// 或者用反射
try {
$reflectionClass = new ReflectionClass($str);
$b = $reflectionClass.newInstance();
return $b;
}
throw ClassNotFoundException;
複製程式碼
要根據類名動態生成示例,首先要判斷類是否存在吧?PHP中與之相關的是class_exists
函式和ReflectionClass
類。在上面的例子中,只傳入字串 "B",class_exists
回返回true
嗎?
答案是否定的。class_exists
和ReflectionClass
只會在全域性類列表中根據名字查詢,不會理會呼叫函式所在(或引入)的名字空間。同理,如果使用use
引入類名並做別名(as
),別名類在class_exists
中也會返回false
。
那麼PHP能否改進一下class_exists
和ReflectionClass
的行為,讓其根據當前上下文判斷?
可以這麼做,但是代價很大,原因包括:
-
class_exists
和ReflectionClass
都沒有指示程式上下文Context
的引數; -
PHP比較坑的一點:類名不會像函式、常量一樣往上逐級查詢;
-
如果存在多個同名的類,載入哪個?如以下程式碼所示:
不管採取哪種行為,都會招致吐槽。
保持目前的情況,除動態生成例項時需要完全限定類名,並無其他槽點。並且實現上簡單,行為明確且一致。
總結
作為一門指令碼語言,PHP非常的靈活,但也會帶來一些使用上的困惑。本文所討論的根據類名動態生成物件,就要無視當前所在或引入的名字空間,必須使用完全限定類名形式。
作為對比,C++不能動態生成物件。Java要用Class.forName
的方式獲取class
物件,然後再呼叫建構函式生成。Java不能直接new
類名,避免了PHP中的坑,但Class.forName
同樣需要完全限定類名,避免不明確行為。