thePHP.cc Logo Deutsch Contact
Indirect Invocation Considered Harmful

Indirect Invocation Considered Harmful

There is a bug in PHP that prevents scalar type declarations to be interpreted strictly when a function or method is invoked using the Reflection API.

Consider a class C that has a method m which expects a parameter $x of type string:

<?php  declare ( strict_types = 1 ) ;
class   C
{
     public   function   m ( string   $x )
     {
         var_dump ( $x ) ;
     }
}

Let us try to pass an integer instead of a string, which, when we enable strict interpretation of scalar type declarations, should yield a type error.

When we invoke that method directly, we get the error we expect:

<?php  declare ( strict_types = 1 ) ;
$o   =   new   C ;
$o -> m ( 1 ) ;

Executing the code shown above will print the output shown below:

Fatal error: Uncaught TypeError: Argument 1 passed to C::m() must be of the type string, integer given, called in ...

If we, however, invoke the method indirectly then PHP will not generate the expected type error.

<?php  declare ( strict_types = 1 ) ;
 
$object      =   new   C ;
$arguments   =   [ 1 ] ;
 
$method   =   new   ReflectionMethod ( $object ,   'm' ) ;
$method -> invokeArgs ( $object ,   $arguments ) ;

Instead, executing the code shown above will print the output shown below:

string(1) "1"

While the discussion of that bug mentions that other forms of indirect invocation might also be affected, we were only able to reproduce the bug for the Reflection API. We tried callbacks with functions such as array_map() , the call_user_func() function, and the $object->$method() syntax. For all these forms of indirect invocation a type error was raised, as is expected and correct.

In the example above, the Reflection API is used to invoke a method with arguments coming from an array. The same effect can be achieved using argument unpacking:

<?php  declare ( strict_types = 1 ) ;
 
$object      =   new   C ;
$arguments   =   [ 1 ] ;
 
$object -> m ( ... $arguments ) ;

Executing the code shown above will once again print:

Fatal error: Uncaught TypeError: Argument 1 passed to C::m() must be of the type string, integer given, called in ...

We hope that developers do not use this bug in PHP to bypass type safety in code that they call – and, of course, that this bug will be fixed soon.