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?

Web mentions

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

      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

      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

      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

          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) ;)