クロージャを変数に再割り当てしてから呼び出すことなく、オブジェクトのプロパティに直接割り当てるクロージャを呼び出すことができるようにしたいと思います。これは可能ですか?
以下のコードは機能せず、Fatal error: Call to undefined method stdClass::callback()
を引き起こします。
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback();
PHP7以降、次のことができます
$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');
または Closure :: call() を使用しますが、StdClass
では機能しません。
PHP7より前は、マジック__call
メソッドを実装して呼び出しをインターセプトし、コールバックを呼び出す必要がありました(もちろん__call
メソッドを追加できないため、StdClass
には不可能です) )
class Foo
{
public function __call($method, $args)
{
if(is_callable(array($this, $method))) {
return call_user_func_array($this->$method, $args);
}
// else throw exception
}
}
$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');
できないことに注意してください
return call_user_func_array(array($this, $method), $args);
__call
本文で、これは無限ループで__call
をトリガーするためです。
これは、オブジェクトが関数のように動作するために使用する魔法のメソッドであるため、クロージャで__invokeを呼び出すことで実行できます。
$obj = new stdClass();
$obj->callback = function() {
print "HelloWorld!";
};
$obj->callback->__invoke();
もちろん、コールバックが配列または文字列(PHPでも有効なコールバックである可能性がある)の場合は機能しません-クロージャーおよび__invoke動作を持つ他のオブジェクトの場合のみ。
PHP 7 の時点で、次のことができます:
($obj->callback)();
PHP 7クロージャーは call()
メソッドを使用して呼び出すことができます:
_$obj->callback->call($obj);
_
PHP 7は、任意の_(...)
_式でも操作を実行できるため( コリクルム ):
_($obj->callback)();
_
他の一般的なPHP 5アプローチは次のとおりです。
マジックメソッド __invoke()
の使用( Brilliand で説明)
_$obj->callback->__invoke();
_
call_user_func()
関数を使用する
_call_user_func($obj->callback);
_
式で中間変数を使用する
_($_ = $obj->callback) && $_();
_
それぞれの方法には長所と短所がありますが、最も根本的で決定的な解決策は Gordon によって提示されたもののままです。
_class stdKlass
{
public function __call($method, $arguments)
{
// is_callable([$this, $method])
// returns always true when __call() is defined.
// is_callable($this->$method)
// triggers a "PHP Notice: Undefined property" in case of missing property.
if (isset($this->$method) && is_callable($this->$method)) {
return call_user_func($this->$method, ...$arguments);
}
// throw exception
}
}
$obj = new stdKlass();
$obj->callback = function() { print "HelloWorld!"; };
$obj->callback();
_
call_user_func()
を使用することで可能になるようです。
call_user_func($obj->callback);
しかし、エレガントではありません。
私はこれが古いことを知っていますが、PHP 5.4+
最初に、プロパティを呼び出し可能にする特性を作成します。
trait CallableProperty {
public function __call($method, $args) {
if (property_exists($this, $method) && is_callable($this->$method)) {
return call_user_func_array($this->$method, $args);
}
}
}
次に、クラスでその特性を使用できます。
class CallableStdClass extends stdClass {
use CallableProperty;
}
これで、匿名関数を介してプロパティを定義し、それらを直接呼び出すことができます。
$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4
オブジェクトプロパティをクロージャとして正常に呼び出す別の方法を次に示します。
コアオブジェクトを変更したくない場合は、これを使用します。
$obj = new AnyObject(); // with or without __invoke() method
$obj->callback = function() {
return function () {
print "HelloWorld!";
};
};
$obj->callback();
更新:
$obj = new AnyObject(); // with or without __invoke() method
$obj->callback = function() {
print "HelloWorld!";
};
$callback = $obj->callback;
$callback();
クロージャーを変数に格納し、変数を呼び出すことは実際には(奇妙に)速くなり、呼び出し量に応じてかなり多くなります。xdebug(非常に正確な測定)で、 1,5(係数は、__ invokeを直接呼び出す代わりに、変数を使用することにより、代わりに、変数にクロージャーを格納して呼び出すだけです。
受け入れられた答えに基づいた別の代替手段がありますが、stdClassを直接拡張します。
class stdClassExt extends stdClass {
public function __call($method, $args)
{
if (isset($this->$method)) {
$func = $this->$method;
return call_user_func_array($func, $args);
}
}
}
使用例:
$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();
おそらくcall_user_func
または__invoke
しかし。
PHP 5.4以上を使用している場合、呼び出し可能オブジェクトをオブジェクトのスコープにバインドして、カスタム動作を呼び出すことができます。たとえば、次のように設定する場合。
function run_method($object, Closure $method)
{
$prop = uniqid();
$object->$prop = \Closure::bind($method, $object, $object);
$object->$prop->__invoke();
unset($object->$prop);
}
そして、あなたはそのようなクラスで働いていました..
class Foo
{
private $value;
public function getValue()
{
return $this->value;
}
}
オブジェクトのスコープ内から操作しているように、独自のロジックを実行できます
$foo = new Foo();
run_method($foo, function(){
$this->value = 'something else';
});
echo $foo->getValue(); // prints "something else"