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.