在框架開發,模組化開發等場合,我們可能有一種需求,那就是在PHP運行時動態實例化物件。
什麼是動態實例化物件呢?我們先來看PHP有一種變數函數(可變函數)的概念,例如以下程式碼:
function foo() { echo 'This is the foo function'; } $bar = 'foo'; $bar();
執行上述程式碼將會輸出「This is the foo function」。具體請參考PHP手冊:可變函數。當然,如果需要動態呼叫的話,那麼就使用call_user_func或call_user_func_array函數。這兩個函數的用法不是本文的重點,不懂的同學請查閱其它資料。回到本文的話題:什麼是動態實例化物件?本人認為動態實例化對象,也就是:需要實例化的對像是在程式運行時(run-time)動態決定(由變數決定)需要實例化什麼樣的對象,而不是直接寫死在程式碼裡。
透過上述例子我們已經知道如何在運行時動態調用一個函數了,在現在面向對向如此流行的今天,在一些程式碼中,我們需要去動態去實例化一個類,該怎麼做呢?
情況一:類別的建構子沒有參數或參數的數量是確定的
如果類別的建構子沒有參數或我們要實例化的類別根本沒有建構函數,似乎簡單一點,可以照上面的例子改一個嘛,嗯,照葫蘆畫瓢,誰不會:
程式碼範例:(建構子沒有參數)
class FOO { private $a, $b; public function construct() { $this->a = 1; $this->b = 2; } public function test() { echo 'This is the method test of class FOO<br />'; echo '$this->a=', $this->a, ', $this->b=', $this->b; } } $bar = 'FOO'; $foo = new $bar(); $foo->test();
運行一下,就看到了輸出瞭如下結果:
This is the method test of class FOO $this->a=1, $this->b=2
嗯,我們要傳參的話,那麼就這樣吧:
class FOO { private $a, $b; public function construct($a, $b) { $this->a = $a; $this->b = $b; } public function test() { echo 'This is the method test of class FOO<br />'; echo '$this->a=', $this->a, ', $this->b=', $this->b; } } $bar = 'FOO'; $foo = new $bar('test', 5); $foo->test();
一樣可以得到類似的結果:
This is the method test of class FOO $this->a=test, $this->b=5
很理想嘛。
情況二:類別的建構函數的參數個數不確定
這種情況就要麻煩很多了,但是如果要寫得比較通用的話,就不得不考慮這種情況了。例如,我們有以下兩個類別
class FOO { public function test() { echo 'This is the method test of class FOO'; } } class BAR { private $a, $b; public function construct($a, $b) { $this->a = $a; $this->b = $b; } public function test() { echo 'This is the method test of class BAR<br />'; echo '$this->a=', $this->a, ', $this->b=', $this->b; } }
我們想要一個通用的方式來實例化這兩個類別。我們注意到FOO類別是沒有寫建構子的,或是可以認為FOO類別的建構子的參數個數為零;而BAR類別的建構子卻有參數。還好,PHP5已經夠強大了,引入了反射的概念,具體請參考PHP手冊:反射,雖然手冊上也沒有什麼可參考的:)。還好,命名寫得不錯,從類別名稱和方法名稱上面已經可以看出大概的端倪,不需要太多的文字。
那麼好吧,就讓我們用PHP5的反射來著手這個事情:
(還在用PHP4的同學請不要走開,如果你擁有一個沒有反射的PHP版本或者是你為了相容也好還是不想升級也好,反正不想用反射的,下面有解決方案)
$class = new ReflectionClass('FOO'); $foo = $class->newInstance(); //或者是$foo = $class->newInstanceArgs(); $foo->test();
看到什麼了沒有?接著來:
$class = new ReflectionClass('BAR'); $bar = $class->newInstanceArgs(array(55, 65)); $bar->test();
OK,似乎可以了,那麼就整理一下吧,來個通用函數,我們想這麼設計,此函數的第一個函數是要實例化的類別名,從第二個參數開始就是要實例化類別的建構子的參數,有幾個就寫幾個上去,沒有就不寫。要實作參數個數可變的函數,我們有兩種方法:
第一種是類似:
function foo($arg1, $arg2 = 123, $arg3 = 'test', $arg4 = null, ....... ) { //some code; }
的辦法,這個方法有兩個缺點,第一個是你如果需要傳100個參數就寫100個參數嗎?第二個是你還要在程式裡判斷哪個參數是不是null,或是其它預設值。 (題外話:這種寫法的參數預設值必須放在最後,你不能將沒有預設值的參數插在有預設值的中間或前面,否則你呼叫的時候也必須明確地寫上有預設值參數的值)
另一種實作參數數量可變的方法是在函數裡用PHP的內建函數func_get_args(猛擊這裡看手冊)取得傳給函數的參數。類似的函式有func_get_num和func_get_arg,算了,我懶,你們自己找手冊看吧。
那麼,用這個函數似乎要方便很多,我們根據想像的函數參數的排列,程式碼應該是這個樣子的:
function newInstance() { $arguments = func_get_args(); $className = array_shift($arguments); $class = new ReflectionClass($className); return $class->newInstanceArgs($arguments); }
OK,讓我們來看效果:
$foo = newInstance('FOO'); $foo->test(); //输出结果: //This is the method test of class FOO $bar = newInstance('BAR', 3, 5); $bar->test(); //输出结果: //This is the method test of class BAR //$this->a=3, $this->b=5
短短四行程式碼,效果相當完美啊。那麼,如果應用到類別裡面,我們可以利用這個思想,直接寫成魔術方法,可以讓我們的類別更酷哦!
class INSTANCE {
function call($className, $arguments) {
$class = new ReflectionClass($className);
return $class->newInstanceArgs($arguments);
}
}
$inst = new INSTANCE();
$foo = $inst->foo();
$foo->test();
//输出结果:
//This is the method test of class FOO
$bar = $inst->bar('arg1', 'arg2');
$bar->test();
//输出结果:
//This is the method test of class BAR
//$this->a=3, $this->b=5
咔,##
function foo($a, $b) { echo '$a=', $a, '<br />'; echo '$b=', $b; } call_user_func_array('foo', array(1, 'string')); //本例输出结果: //$a=1 //$b=string############咔咔,酷吧。 ###
接下来讨论一下不使用反射类的情况。例如PHP4中就没有反射,而一些老项目就是运行在PHP4上面的。或者是要保证项目对未知环境的兼容性,Whatever,来关心一下怎么动态传参吧。PHP中动态传参的函数只有一个:call_user_func_array(轻击此处查看手册)。这是一个动态调用函数的函数,作用是可以将函数的参数以数组的形式传递给要调用的函数。好吧,我自己也被自己绕晕了,直接来看实例:
function foo($a, $b) { echo '$a=', $a, '<br />'; echo '$b=', $b; } call_user_func_array('foo', array(1, 'string')); //本例输出结果: //$a=1 //$b=string
那么,要实现用这种方法来动态实例化对象并传参,呃……,只有曲线救国了,我们得先写一个函数,让这个函数来实例化对象,而这个函数的参数就原原本本地传给要实例化对象的类的构造函数就好了。打住!那这个函数得有几个参数啊?怎么实现传递不同个数的参数呢?嘿嘿,我一声冷笑,你忘了PHP里提供一个创建匿名函数的函数吗?(又开始绕起来了……)create_function(手册在此),照着手册里面的例子直接画就可以了,我也懒得打字了,直接看下面的代码,注释我写清楚点大家都明白了:
function newInst() { //取得所有参数 $arguments = func_get_args(); //弹出第一个参数,这是类名,剩下的都是要传给实例化类的构造函数的参数了 $className = array_shift($arguments); //给所有的参数键值加个前缀 $keys = array_keys($arguments); array_walk($keys, create_function('&$value, $key, $prefix', '$value = $prefix . $value;'), '$arg_'); //动态构造实例化类的函数,主要是动态构造参数的个数 $paramStr = implode(', ',$keys); $newClass=create_function($paramStr, "return new {$className}({$paramStr});"); //实例化对象并返回 return call_user_func_array($newClass, $arguments); }
好了,至于效果是什么,就麻烦各位看官自己动动手,运行一下看看,是不是自己期望的结果。
如果改写成类里面的魔术方法,哼哼,威力嘛,你懂的!然后呢,我还是懒了,如果要写成魔术方法的话,相信这么easy的事情你很轻松就办到了。就当一个作业吧。另,本文的代码都是本人运行过的,但是,写文章的时候没有使用复制/粘贴功能,所以,你最好是也不要复制粘贴。如果从本文中的代码copy下来运行出错的话,还烦请各位自己debug一下,编程不就是要自己写么。
本来这个话题到这里就应该可以结束了,但是想到我在想到这个办法之前用的方法(好绕……),本着重在交流的态度,一起放出来。我例两个关键函数吧:extract和eval。只是我个人觉得用eval函数比较山寨,能不用最好不用。于是又想出了上面的办法,哈哈,编程最重要的是思想,不是吗?好,又是一道作业题了,大家可以试试用这两个函数(当然也会用到别的函数)来实现带不定数量的参数动态实例化对象的函数。
以上是php動態實例化物件並向建構函式傳遞參數的案例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!