This is a story about how my own code broke a site in development after migrating from PHP 5 to PHP 7, and what I did to fix it.
On December 3 2015, PHP 7 was released. Along with significant speed increases, there were some breaking changes that make it not 100% backward compatible with PHP 5. Note that PHP 6 never actually existed (seriously), so that’s why it’s not mentioned here.
One of the breaking changes in PHP 7 was the following:
Indirect access to variables, properties, and methods will now be evaluated strictly in left-to-right order, as opposed to the previous mix of special cases.
– PHP Manual [link]
Consider the following example:
$action = array( 'callback' => 'my_function', ... );
$action['callback']();
Effectively, we are just invoking the function my_function in a fancy way that makes use of an array key/value pair. You may be thinking you’d never do this in the real world without additional context (if even then). I doubt that I will either in the future, even if the surrounding context makes it really tempting.
This isn’t actually an issue that PHP 7 seems to care about, though. It will do the above just fine.
However, if we add another variable layer into the mix, this is where we get the breaking changes introduced in PHP 7.
Consider the following code, which works fine in PHP 5:
$action = array( 'callback' => 'my_function', ... );
$object = new Random_Object();
$object->$action['callback']();
The PHP 5 result is identical to:
$object->my_function();
But now, when we upgrade to PHP 7 we’re going to have a fatal error!
In PHP 7, they decided to always interpret variable names left-to-right. Even though it breaks my code here, I’m actually glad that there is going to be a standard and not a mixed set of different rules.
The new way the above code will be interpreted in PHP 7 is as follows. I have broken it down into two logical steps, for illustrative purposes.
First, PHP looks directly at $object->$action (remember, left-to-right), which doesn’t even exist. It also doesn’t make sense as an expression because $action is an array, and we can’t use an array to access a property or method name for an object.
In effect, we have this being interpreted as an intermediate line of code:
$nothing_useful = $object->$action;
// Notice: array to string conversion
Basically, $nothing_useful has been created indirectly for us by PHP, and its value is NULL. From here, we can’t expect anything good to happen. The result is that we are calling a function whose name is a NULL variable.
$big_oops = $nothing_useful['callback']();
// Fatal error: function name must be a string
Luckily, this isn’t the worst thing to have to fix. As mentioned above, it makes me think that I should be way more careful about taking these types of shortcuts, and probably always use call_user_func or call_user_func_array in these kinds of scenarios.
Either way, it’s not too difficult to change the above code to make both PHP 5 and PHP 7 happy. We just have to be a little more careful with our handling of the array and the object:
$action = array( 'callback' => 'my_function', ... );
$function_name = $action['callback'];
$object = new Random_Object();
$object->$function_name();
And that’s it! Just one extra line of code can make us both forward and backward compatible, and that’s a pretty good place to be.
Leave a Reply