10

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?

2
  • 5
    why it is a downside ? Commented Sep 8, 2015 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 Commented Sep 8, 2015 at 13:47

3 Answers 3

11

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

Sign up to request clarification or add additional context in comments.

2 Comments

that really looks like a hack! Thanks for the answer
@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
6

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.

Comments

5

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.