ReflectionMethodクラスに定義されている、invokeメソッドとinvokeArgsメソッドについて。
第1引数は必ず$method->classのインスタンスでなければならない
第1引数は、$method->classもしくは$method->classを継承したクラスのインスタンスじゃなきゃ駄目。
つまり、instanceofでtrueになるクラスのインスタンスじゃなきゃ駄目。
テストソース
<?php class HelloWorld { public function sayHelloTo($name) { return 'Hello ' . $name . "\n"; } } class HelloWorld2 extends HelloWorld { } class HelloWorld3 { public function sayHelloTo($name) { return 'Hello ' . $name . "\n"; } } $reflectionMethod = new ReflectionMethod('HelloWorld', 'sayHelloTo'); echo $reflectionMethod->invokeArgs(new HelloWorld(), array('Mike')); echo $reflectionMethod->invokeArgs(new HelloWorld2(), array('Mike')); echo $reflectionMethod->invokeArgs(new HelloWorld3(), array('Mike'));
テスト実行結果
Hello Mike Hello Mike PHP Fatal error: Uncaught exception 'ReflectionException' with message 'Given object is not an instance of the class this method was declared in' in /private/tmp/ref.php:19 Stack trace: #0 /private/tmp/ref.php(19): ReflectionMethod->invokeArgs(Object(HelloWorld3), Array) #1 {main} thrown in /private/tmp/ref.php on line 19
何がしたかったのか
前に作ったAnonymousClassに、普通のクラスのオブジェクトをAnonymousClassオブジェクトに変換するメソッドを追加したかった。
今回だけじゃなく、ReflectionMethodでは__call()で黒魔術が出来ない。
<?php class AnonymousClass { public static function createByObject($object) { if (!is_object($object)) { $func = get_called_class().'::createByObject()'; throw new AnonymousClass_Exception('Argument 1 passed to '.$func.' must be an instance of some class'); } if ($object instanceof AnonymousClass) { return $object; } $self = new self(); $ref = new ReflectionObject($object); $methods = $ref->getMethods(); foreach ($methods as $method) { $name = $method->getName(); $self->$name = function($self) use($method) { $args = func_get_args(); $self = array_shift($args); return $method->invokeArgs($self, $args); // error }; } return $self; } }
そもそもの原因
今回の場合は、渡されたオブジェクトがクラスを継承しているかをチェックしていた。
メソッドがなかった時に「PHP Fatal error: Call to undefined method」が出ちゃうからじゃないかと。
if (!instanceof_function(obj_ce, mptr->common.scope TSRMLS_CC)) { efree(params); _DO_THROW("Given object is not an instance of the class this method was declared in"); /* Returns from this function */ }
昨今、そのFatal errorが出ないようにExceptionを発生させるラップがあらゆる箇所で定義されている。
そもそもこれはメソッドを持つオブジェクトが発生させるべき例外。
PHPのwarning文化は、いつか区切りをつけて廃止しなきゃいけない。
どうすれば綺麗に問題解決出来るか
ReflectionMethod::invoke()を今自分が望むように動くようにするだけなら、is_callable()を使えば問題解決出来る。
is_callable()は__call()が定義されていると必ずtrueを返す。
でもReflectionってオブジェクト指向なクラス群だから、それじゃあクラスの目的として駄目。
<?php class Test { public function explicit() { // ... } public function __call($meth, $args) { // ... } } $Tester = new Test(); var_dump(method_exists($Tester, 'anything')); // false var_dump(is_callable(array($Tester, 'anything'))); // true
どうすれば綺麗に統一出来るか
property_exists()やmethod_exists()が、__get()や__call()に対応すれば万事解決。
オーバーライド出来るように、__isset()みたいなマジックメソッドがあればいいのではないかと。