PHP - Callable method not as array

Yes, there is... even though it's a horrid, horrid hack:

$foo = new Foo();

function makeCallable($instance, $method)
{
    return function() use ($instance, $method) {
        return $instance->{$method}();//use func_get_args + call_user_func_array to support arguments
    };
}

Then you can use:

$callable = makeCallable($foo, 'bar');
var_dump(is_array($callable));//false

The downside is that:

var_dump($callbale instanceof Closure);//true

Basically, don't pay any attention to the fact that your callable is also an array, and just use type-hinting throughout your code base:

function foobar(callable $action)
{
    return call_user_func($action);
}

That ought to work just fine.

On why you feel the current callable array is a downside: I understand why you feel this is not a good thing. There is indeed no need for anyone to know that a particular callable construct is an array, or a string, or an anonymous function (which is actually an instance of the Closure class - another implementation detail you might not want to pass around). But it's exactly because callable constructs come in many forms, the callable type-hint exists: the code you write that requires a callable needn't care about how that callable entity is implemented, it just needs to know that it can call that piece of information:

function handleEvent(callable $action, array $args = null)
{
    if ($args) {
        return call_user_func_array($action, $args);
    }
    return call_user_fun($action);
}

No need for me to check if $action is a string (like 'strtolower', a Closure instance or an array) I just know I can call it


Functions and methods are not first class citizens in PHP, i.e. they cannot be assigned to variable, do not have a type etc.

callable isn't actually a type, is_callable as well as the callable type hint just checks if something can be used as a function. This can be:

  • an array [class, method] for a static method
  • an array [object, method] for an instance method
  • a string for a function
  • a Closure instance
  • an object that implements the magic method __invoke()

As you see, all of these values actually have a different type and just happen to be "callable". There is no such thing as a "pure" callable that does not have another type.


PHP 7.1 finally took a big steep in making callbacks first-class citizens via Closure::fromCallable (RFC).

As described here, you can use it like this:

class MyClass
{
  public function getCallback() {
    return Closure::fromCallable([$this, 'callback']);
  }

  public function callback($value) {
    echo $value . PHP_EOL;
  }
}

$callback = (new MyClass)->getCallback();
$callback('Hello World');

Using it has some benefits:

  • Better error handling - When using Closure::fromCallable, it shows errors in the right place instead of showing it where we are using the callable. This makes debugging a lot easier.

  • Wrapping scopes - The above example will work fine even if the callback is a private/protected method of MyClass.

  • Performance - This can also improve the performance by avoiding the overhead of checking if the given callable is actually a callable.

Tags:

Php

Callable