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


109

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

以下のコードは機能せず、原因となりFatal error: Call to undefined method stdClass::callback()ます。

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

1
これはまさにあなたが必要とするものです:github.com/ptrofimov/jslikeobjectさらに:クロージャーの中で$ thisを使用し、継承を使用できます。PHP> = 5.4のみ!
Renato Cuccinotto 2013年

回答:


106

PHP7以降、次のことができます

$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');

または、Closure :: call()を使用しますが、これはで機能しませんStdClass


PHP7より前のバージョンでは、マジック__callメソッドを実装して、呼び出しをインターセプトし、コールバックを呼び出す必要があります(StdClassもちろん、__callメソッドを追加できないため、これは不可能です)。

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無限ループに。


2
時々、あなたはこの構文が役に立つと思うでしょう-call_user_func_array($ this-> $ property、$ args); メソッドではなく、呼び出し可能なクラスプロパティに関する場合。
Nikita Gopkalo 2014年

104

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

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

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


3
@marcioAlmadaはとても醜いです。
Mahn、2014

1
@マーンそれは受け入れられた答えよりも明確だと思います。この場合は、明示的な方が適切です。あなたが本当に「かわいい」解決策を気にかけているcall_user_func($obj->callback)なら、それは悪いことではありません。
marcio 14

しかしcall_user_func、文字列でも機能し、それが常に便利であるとは限りません
Gherman

2
@cerebriformは、意味的には、それ$obj->callback->__invoke();が予想されるときに行う必要がある意味を持ちません$obj->callback()。それは一貫性の問題です。
Mahn、2015

3
@マーン:確かに、それは一貫していません。ただし、一貫性はPHPの強力なスーツではありませんでした。:)しかし、オブジェクトの性質を考えると、それは理にかなっていると思います$obj->callback instanceof \Closure
ビショップ


10

PHP 7以降、call()メソッドを使用してクロージャを呼び出すことができます。

$obj->callback->call($obj);

以来、PHP 7任意の操作を実行することができる(...)(することによって説明されるようにすぎ式Korikulum)。

($obj->callback)();

他の一般的なPHP 5のアプローチは次のとおりです。

  • 魔法の方法を使用する__invoke()ブリリアンによって説明されているように)

    $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

を使って可能だそうcall_user_func()です。

call_user_func($obj->callback);

しかし、エレガントではありません... @Gordonの言うことはおそらく唯一の方法です。


7

まあ、あなたが本当に主張するなら。別の回避策は次のとおりです。

$obj = new ArrayObject(array(),2);

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

$obj['callback']();

しかし、それは最も良い構文ではありません。

しかし、PHPパーサは常に扱いT_OBJECT_OPERATORIDENTIFIER(メソッド呼び出しとして。->メソッドテーブルをバイパスし、代わりに属性にアクセスするための回避策はないようです。


7

私はこれが古いことを知っていますが、PHP 5.4以降を使用している場合、Traitsはこの問題をうまく処理すると思います

まず、プロパティを呼び出し可能にする特性を作成します。

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

うわー:)これは私がやろうとしていたことよりもはるかにエレガントです。私の唯一の質問は、英国のホット/コールドタップコネクタ要素の場合と同じです。なぜそれがまだ組み込まれていないのですか?
dkellner 2015年

ありがとう:) あいまいさがあるため、おそらく組み込まれていません。私の例で、「add」と呼ばれる関数と「add」と呼ばれるプロパティが実際にあった場合を想像してみてください。括弧があると、PHPはその名前の関数を探すようになります。
SteveK

2

まあ、クロージャを変数に格納し、変数を呼び出す方が実際には(非常に)高速であることを強調する必要があります。呼び出し量に応じて、xdebug(非常に正確な測定)を使用すると、かなり大きくなります。 1,5(__invokeを直接呼び出すのではなく、変数を使用することによる係数。そのため、代わりに、クロージャを変数に格納して呼び出すだけです。


2

更新しました:

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

PHP> = 7:

($obj->callback)();

PHP> = 5.4:

$callback = $obj->callback;  
$callback();

やってみましたか?動かない。Call to undefined method AnyObject::callback()(もちろん、クラスAnyObjectが存在します。)
Kontrollfreak

1

受け入れられた回答に基づいて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


0

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

これはPHP5.5で機能することに注意してください

$a = array();
$a['callback'] = function() {
    print "HelloWorld!";
};
$a['callback']();

クロージャーの疑似オブジェクトコレクションを作成できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.