Coder Perfect

Calling closure assigned to object property directly

Problem

I’d like to be able to call a closure that I’ve assigned to a property on an object without first reassigning it to a variable and then calling it. Is that even possible?

The following code does not work and results in a fatal error: stdClass::callback is an undefined method ().

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

Asked by Kendall Hopkins

Solution #1

You may now accomplish this with PHP7.

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

Alternatively, you can use Closure::call(), albeit this won’t work on a StdClass.

To intercept the call and invoke the callback before PHP7, you’d have to implement the magic __call method (which isn’t possible for StdClass, because you can’t 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');

You should be aware that you won’t be able to

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

__call would be triggered in an infinite loop if this was done in the __call body.

Answered by Gordon

Solution #2

Because __invoke is the magic method that objects utilize to behave like functions, you can do this by invoking it on the closure:

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

That won’t work if the callback is an array or a string (both of which are valid callbacks in PHP), but it will work for closures and other objects with __invoke behavior.

Answered by Brilliand

Solution #3

You can now accomplish the following with PHP 7:

($obj->callback)();

Answered by Korikulum

Solution #4

Since PHP 7, you may use the call() function to call a closure:

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

Since PHP 7, operations can be performed on arbitrary (…) expressions as well (as shown by Korikulum):

($obj->callback)();

Other popular PHP 5 techniques include:

Each approach has advantages and disadvantages, but Gordon’s proposal remains the most radical and conclusive.

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

Answered by Daniele Orlando

Solution #5

Using call user func, it appears to be doable ().

call_user_func($obj->callback);

However, it is not elegant…. What @Gordon suggests is most likely the only option.

Answered by Pekka

Post is based on https://stackoverflow.com/questions/4535330/calling-closure-assigned-to-object-property-directly