PHP's callable typehint too loose?
PHP got support for closures in version 5.3, and in PHP 5.4 we got support for
a callable
typehint, that can be used as such:
<?php
function callMe(callable $callBack) {
$callBack();
}
?>
All these little changes make it feel more comfortable to apply functional programming concepts to PHP, but occasionally we need to drop back to using less aesthetically pleasing code.
To use our callMe
function to call back directly to a object method, we
use this syntax:
<?php
callMe([$object, 'method']);
?>
This also works with static methods:
<?php
callMe(['\\Namespace\\MyClass', 'method']);
?>
Or if you are on PHP 5.5:
<?php
callMe([MyClass::class, 'method']);
?>
These all work, because when you execute this code:
<?php
$foo = [$object, 'method'];
$foo();
?>
…PHP will actually correctly execute method
on our $object
. But there’s an
exception. Aside from the previous syntax for calling static methods, PHP also
allows this to fulfill the callable
typehint:
callMe('MyClass::method');
Unfortunately, this will trigger the following error:
Fatal error: Call to undefined function MyClass::method() in /in/Kkr3F on line 14
Process exited with code 255.
The only remedy is to change our original function from:
<?php
function callMe(callable $callBack) {
$callBack();
}
?>
to:
<?php
function callMe(callable $callBack) {
call_user_func($callBack);
}
?>
call_user_func($callBack);
does what $callBack()
does, but it’s less
aesthetically pleasing.
So if you rely on the callable
typehint, or the is_callable()
function, be
aware that you need to use call_user_func()
to get reliable results.
Lastly
I came to write this because I got a bug report from someone who wanted to be
able to use the 'MyClass::method'
syntax. I was a bit surprised this didn’t
work, as I had always used ['MyClass', 'method']
. So, before changing all my
$x()
to call_user_func($x)
, I first had to figure out how this was
possible.
What do you think? Is this a PHP bug? Or was I wrong to think that this should have worked in the first place?
Comments
Matthew Weier O'Phinney •
I'd argue it's a PHP bug, as it means there's an inconsistency between what the typehint callable deems valid, and what can actually be directly invoked by PHP. This is exactly the sort of inconsistency that Zeev and Dmitry were also attempting to fix with their alternate STH patch (not with regards to STH, but with regards to ensuring the engine casting rules are implemented consistently).
Evert •
Looks like there was a bug already! https://bugs.php.net/bug.ph...
Olivier Laviale •
What's worse is that is_callable() says "a::b" is callable, but currently it isn't.
Nicolas Grekas •
See https://bugs.php.net/bug.ph...
Evert •
Thanks! Glad to see I was not alone =)
Aaron Piotrowski •
Any idea if this has been fixed in PHP7?
I was looking forward to changing the call_user_func_array($callback, $args) calls in my code to $callback(...$args). Instead, now it looks like they'll be staying and I may have to go change $callback() to call_user_func($callback).
Evert •
Unfortunately not yet. Take a look here:
http://3v4l.org/Kkr3F
Aaron Piotrowski •
Now I'm debating if I want to just tell people to not use the "Class::method" syntax or actually change all my code to support it.
I could also consider finding the code that makes $callback() possible in the interpreter and see if I could patch it, then do the same for PHP 5.5 and 5.6.
Evert •
A patch would be awesome ;) check the bug in one of the other comments.
I had the same thought myself about telling people not use the syntax vs. using call_user_func everywhere. Since it's considered a PHP bug, I'm leaning towards telling people to stop using it.
Aaron Piotrowski •
From the comments on that bug report, it looks like I'd have better luck if I removed the compatibility from is_callable() and the callable type-hint rather than try and support it in the $callback() syntax.
I think I will also just advise people not to use it, since it's just duplication of ['ClassName', 'methodName'].
mnapoli •
Just a quick update: it was actually implemented in PHP 7 (after your comment). If you follow the link you can see it now works correctly.
That will avoid raising doubts to future readers (like me) ;)