9

Usually in PHP if you have a method bar of a class Foo and you'd like to pass it around as a callable, you use something like

$foo = new Foo();
$callable = [$foo, 'bar'];

The downside of this is that is_array($callable) evaluates to true.

Is there another feasible way to pass around a class method as a callable such that is_array($callable) returns false?

marcosh
  • 8,079
  • 2
  • 41
  • 64
  • 5
    why it is a downside ? – Halayem Anis Sep 08 '15 at 13:45
  • @HalayemAnis I understand that it is not a clear downside, but I see it like one because I wouldn't expect a callable to be an array. It seem something like an implementation detail that should not be passed around – marcosh Sep 08 '15 at 13:47

3 Answers3

10

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

Elias Van Ootegem
  • 67,812
  • 9
  • 101
  • 138
  • that really looks like a hack! Thanks for the answer – marcosh Sep 08 '15 at 13:52
  • @marcosh: was typing up a quick rant about why you really shouldn't care about what the callable looks like: there are many valid callable/callback constructs in PHP, only one of which is an array – Elias Van Ootegem Sep 08 '15 at 13:53
4

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.

Fabian Schmengler
  • 22,450
  • 8
  • 70
  • 104
4

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.

geek-merlin
  • 662
  • 7
  • 11