PHP 執行期動態產生 class instance
今天在聽 小鐵 講 DI 實作 1 時提到了這個例子,當下在想如果換成 PHP 5.6 的 arguments unpack 的語法不曉得會不會動,驗證完後覺得很有趣,順便來寫一篇。
不像 Java,PHP 在執行期時才決定要產生哪個類別的 instance 或呼叫哪個函式,並不需要大費周章的用 Reflection API,但還是有些是不透過 Reflection 辦不到的事,其中產生 instance 時如果要傳入參數的話,就需要透過 ReflectionClass 來達成這個目的。
前面提到的問題,你沒辦法用 call_user_func
或 call_user_func_array
來達成目的,因為沒辦法滿足第一個 callable 參數...
-
建構子不是 static method,你沒辦法用
Foo::__construct
這種方式,如果可以的話,參數的傳遞是另外一個問題。 -
在執行建構子時你需要傳入 instance,你都要靠它產生 instance 了,它還跟你討 instance,這兩個條件互斥了,變成雞生蛋與蛋生雞的問題,所以沒辦法用
array($instance, '__construct')
。
PoC 的程式碼如下,跑一下 benchmark
<?php
// bench.php
function bench($callback)
{
$start = microtime(true);
$callback();
return (microtime(true) - $start);
}
class Foo
{
public function __construct($a, $b, $c)
{
// var_dump(func_get_args());
}
}
$class_name = 'Foo';
$loop = 5000000;
$elapsed_time = bench(
function() use ($class_name, $loop) {
global $argv;
$i = 0;
while ($i++ < $loop) {
$ref_class = new ReflectionClass($class_name);
$instance = $ref_class->newInstanceArgs($argv);
}
}
);
echo "Reflection take $elapsed_time seconds\n";
$elapsed_time = bench(
function() use ($class_name, $loop) {
global $argv;
$i = 0;
while ($i++ < $loop) {
$instance = new $class_name(...$argv);
}
}
);
echo "Arguments unpack take $elapsed_time seconds\n";
輸出如下,可以看出效率快一點點,另外的優點是拿 splat operator ...
來換兩行 ReflectionClass 的寫法變的簡潔有力!
shell> time ~/.phpbrew/php/php-5.6.0RC4/bin/php bench.php foo bar
Reflection take 8.2250480651855 seconds
Arguments unpack take 2.4252769947052 seconds
real 0m10.669s
user 0m10.656s
sys 0m0.008s
提外話:
在過程中還發生另外的小插曲,在利用 phpbrew build 5.6.0RC4 來測試上面的 PoC,發現在 build unstable 的版本時動不了,動手修正後送 pull request 2 回去,不久後收到 comment 的回覆的信件時,順便被告知 PHP 5.6.0 GA 了!!也太巧了吧...XD