web-dev-qa-db-ja.com

オブジェクトプロパティに直接割り当てられたクロージャを呼び出す

クロージャを変数に再割り当てしてから呼び出すことなく、オブジェクトのプロパティに直接割り当てるクロージャを呼び出すことができるようにしたいと思います。これは可能ですか?

以下のコードは機能せず、Fatal error: Call to undefined method stdClass::callback()を引き起こします。

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();
98
Kendall Hopkins

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をトリガーするためです。

92
Gordon

これは、オブジェクトが関数のように動作するために使用する魔法のメソッドであるため、クロージャで__invokeを呼び出すことで実行できます。

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback->__invoke();

もちろん、コールバックが配列または文字列(PHPでも有効なコールバックである可能性がある)の場合は機能しません-クロージャーおよび__invoke動作を持つ他のオブジェクトの場合のみ。

99
Brilliand

PHP 7 の時点で、次のことができます:

($obj->callback)();
19
Korikulum

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();
_
7
Daniele Orlando

call_user_func()を使用することで可能になるようです。

call_user_func($obj->callback);

しかし、エレガントではありません。

7
Pekka 웃

私はこれが古いことを知っていますが、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
6
SteveK

オブジェクトプロパティをクロージャとして正常に呼び出す別の方法を次に示します。
コアオブジェクトを変更したくない場合は、これを使用します。

$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();
3
Mohamad Rostami

クロージャーを変数に格納し、変数を呼び出すことは実際には(奇妙に)速くなり、呼び出し量に応じてかなり多くなります。xdebug(非常に正確な測定)で、 1,5(係数は、__ invokeを直接呼び出す代わりに、変数を使用することにより、代わりに、変数にクロージャーを格納して呼び出すだけです。

2
Kmtdk

受け入れられた答えに基づいた別の代替手段がありますが、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しかし。

1
Mahn

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"
0
Lee Davis