Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling closure assigned to object property directly

As of PHP7, you can do

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

or use Closure::call(), though that doesn't work on a StdClass.


Before PHP7, you'd have to implement the magic __call method to intercept the call and invoke the callback (which is not possible for StdClass of course, because you cannot add the __call method)

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');

Note that you cannot do

return call_user_func_array(array($this, $method), $args);

in the __call body, because this would trigger __call in an infinite loop.


You can do this by calling __invoke on the closure, since that's the magic method that objects use to behave like functions:

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

Of course that won't work if the callback is an array or a string (which can also be valid callbacks in PHP) - just for closures and other objects with __invoke behavior.


As of PHP 7 you can do the following:

($obj->callback)();

Since PHP 7 a closure can be called using the call() method:

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

Since PHP 7 is possible to execute operations on arbitrary (...) expressions too (as explained by Korikulum):

($obj->callback)();

Other common PHP 5 approaches are:

  • using the magic method __invoke() (as explained by Brilliand)

    $obj->callback->__invoke();
    
  • using the call_user_func() function

    call_user_func($obj->callback);
    
  • using an intermediate variable in an expression

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

Each way has its own pros and cons, but the most radical and definitive solution still remains the one presented by 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();

It seems to be possible using call_user_func().

call_user_func($obj->callback);

not elegant, though.... What @Gordon says is probably the only way to go.


Well, if you really insist. Another workaround would be:

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

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

$obj['callback']();

But that's not the nicest syntax.

However, the PHP parser always treats T_OBJECT_OPERATOR, IDENTIFIER, ( as method call. There seems to be no workaround for making -> bypass the method table and access the attributes instead.