ReflectionMethod::invoke()が自由じゃない

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()みたいなマジックメソッドがあればいいのではないかと。
 

結論

Reflectionはオブジェクト指向として素晴らしい。
今回の用途に合わなかっただけ。
PHPはもう少し根本を統一させないと言語としてまずい。