Appendix - Backward Compatibility Breaks
If you’re coming from a modern version of PHP (like 5.5 or 5.6), most of your code will probably work fine as-is. Nevertheless, we’ll cover most of the important breaking changes in this section so you’ll be prepared to fix any such issues in your projects.
The majority of these BC breaks were covered in previous chapters. We’ll link back to those sections as needed.
This list comes directly from the php-src UPGRADING document as of 7 November 2015. It may not be exhaustive. Please check https://github.com/php/php-src/blob/PHP-7.0.0/UPGRADING for the latest version.
Language Changes
Variable handling
Left-to-right parsing
Indirect variable, property and method references are now interpreted with left-to-right semantics. Some examples:
$$foo['bar']['baz'] // interpreted as ($$foo)['bar']['baz']
$foo->$bar['baz'] // interpreted as ($foo->$bar)['baz']
$foo->$bar['baz']() // interpreted as ($foo->$bar)['baz']()
Foo::$bar['baz']() // interpreted as (Foo::$bar)['baz']()
To restore the previous behavior add explicit curly braces:
${$foo['bar']['baz']}
$foo->{$bar['baz']}
$foo->{$bar['baz']}()
Foo::{$bar['baz']}()
Global Keywords
The global keyword now only accepts simple variables. Instead of
global $$foo->bar;
it is now required to write the following:
global ${$foo->bar};
Parenthesis influencing behavior
Parentheses around variables or function calls no longer have any influence on behavior. For example the following code, where the result of a function call is passed to a by-reference function
function getArray() { return [1, 2, 3]; }
$last = array_pop(getArray());
// Strict Standards: Only variables should be passed by reference
$last = array_pop((getArray()));
// Strict Standards: Only variables should be passed by reference
will now throw a strict standards error regardless of whether parentheses are used. Previously no notice was generated in the second case.
By-reference assignment ordering
Array elements or object properties that are automatically created during by-reference assignments will now result in a different order. For example
$array = [];
$array["a"] =& $array["b"];
$array["b"] = 1;
var_dump($array);
now results in the array [“a” ⇒ 1, “b” ⇒ 1], while previously the result was [“b” ⇒ 1, “a” ⇒ 1];
list() behavior
Variable assignment order
list() will no longer assign variables in reverse order. For example
list($array[], $array[], $array[]) = [1, 2, 3];
var_dump($array);
will now result in $array == [1, 2, 3] rather than [3, 2, 1]. Note that only the order of the assignments changed, but the assigned values stay the same. E.g. a normal usage like
list($a, $b, $c) = [1, 2, 3];
// $a = 1; $b = 2; $c = 3;
will retain its current behavior.
Empty list assignments
Empty list() assignments are no longer allowed. As such all of the following are invalid:
list() = $a;
list(,,) = $a;
list($x, list(), $y) = $a;
list() no longer supports unpacking strings (while previously this was only supported in some cases). The code
$string = "xy";
list($x, $y) = $string;
will now result in $x == null and $y == null (without notices) instead of $x == “x” and $y == “y”. Furthermore list() is now always guaranteed to work with objects implementing ArrayAccess, e.g.
list($a, $b) = (object) new ArrayObject([0, 1]);
will now result in $a == 0 and $b == 1. Previously both $a and $b were null.
foreach behavior
Interaction with internal array pointers
Iteration with foreach() no longer has any effect on the internal array pointer, which can be accessed through the current()/next()/etc family of functions. For example
$array = [0, 1, 2];
foreach ($array as &$val) {
var_dump(current($array));
}
will now print the value int(0) three times. Previously the output was int(1), int(2) and bool(false).
Array iteration by-value
When iterating arrays by-value, foreach will now always operate on a copy of the array, as such changes to the array during iteration will not influence iteration behavior. For example
$array = [0, 1, 2];
$ref =& $array; // Necessary to trigger the old behavior
foreach ($array as $val) {
var_dump($val);
unset($array[1]);
}
will now print all three elements (0 1 2), while previously the second element 1 was skipped (0 2).
Array iteration by-reference
When iterating arrays by-reference, modifications to the array will continue to influence the iteration. However PHP will now do a better job of maintaining a correct position in a number of cases. E.g. appending to an array during by-reference iteration
$array = [0];
foreach ($array as &$val) {
var_dump($val);
$array[1] = 1;
}
will now iterate over the appended element as well. As such the output of this example will now be “int(0) int(1)”, while previously it was only “int(0)”.
Object iteration
Iteration of plain (non-Traversable) objects by-value or by-reference will behave like by-reference iteration of arrays. This matches the previous behavior apart from the more accurate position management mentioned in the previous point.
Iteration of Traversable objects remains unchanged.
Parameter handling
Duplicate parameter names
It is no longer possible to define two function parameters with the same name. For example, the following method will trigger a compile-time error:
public function foo($a, $b, $unused, $unused) {
// ...
}
Code like this should be changed to use distinct parameter names, for example:
public function foo($a, $b, $unused1, $unused2) {
// ...
}
Retrieving argument values
The func_get_arg() and func_get_args() functions will no longer return the original value that was passed to a parameter and will instead provide the current value (which might have been modified). For example
function foo($x) {
$x++;
var_dump(func_get_arg(0));
}
foo(1);
will now print “2” instead of “1”. This code should be changed to either perform modifications only after calling func_get_arg(s)
function foo($x) {
var_dump(func_get_arg(0));
$x++;
}
or avoid modifying the parameters altogether:
function foo($x) {
$newX = $x + 1;
var_dump(func_get_arg(0));
}
Effect on backtraces
Similarly exception backtraces will no longer display the original value that was passed to a function and show the modified value instead. For example
function foo($x) {
$x = 42;
throw new Exception;
}
foo("string");
will now result in the stack trace
Stack trace:
#0 file.php(4): foo(42)
#1 {main}
while previously it was:
Stack trace:
#0 file.php(4): foo('string')
#1 {main}
While this should not impact runtime behavior of your code, it is worthwhile to be aware of this difference for debugging purposes.
The same limitation also applies to debug_backtrace() and other functions inspecting function arguments.
Integer handling
Invalid octal literals
Invalid octal literals (containing digits larger than 7) now produce compile errors. For example, the following is no longer valid:
$i = 0781; // 8 is not a valid octal digit!
Previously the invalid digits (and any following valid digits) were simply ignored. As such $i previously held the value 7, because the last two digits were silently discarded.
Negative bitwise shifting
Bitwise shifts by negative numbers will now throw an ArithmeticError:
var_dump(1 >> -1);
// ArithmeticError: Bit shift by negative number
Left bitwise shifts
Left bitwise shifts by a number of bits beyond the bit width of an integer will always result in 0:
var_dump(1 << 64); // int(0)
Previously the behavior of this code was dependent on the used CPU architecture. For example on x86 (including x86-64) the result was int(1), because the shift operand was wrapped.
Right bitwise shifts
Similarly right bitwise shifts by a number of bits beyond the bit width of an integer will always result in 0 or -1 (depending on sign):
var_dump(1 >> 64); // int(0)
var_dump(-1 >> 64); // int(-1)
String handling
Hexadecimal numeric strings
Strings that contain hexadecimal numbers are no longer considered to be numeric and don’t receive special treatment anymore. Some examples of the new behavior:
var_dump("0x123" == "291"); // bool(false) (previously true)
var_dump(is_numeric("0x123")); // bool(false) (previously true)
var_dump("0xe" + "0x1"); // int(0) (previously 16)
var_dump(substr("foo", "0x1")); // string(3) "foo" (previously "oo")
// Notice: A non well formed numeric value encountered
filter_var() can be used to check if a string contains a hexadecimal number or convert such a string into an integer:
$str = "0xffff";
$int = filter_var($str, FILTER_VALIDATE_INT, FILTER_FLAG_ALLOW_HEX);
if (false === $int) {
throw new Exception("Invalid integer!");
}
var_dump($int); // int(65535)
Unicode escape sequence
Due to the addition of the Unicode Codepoint Escape Syntax for double-quoted strings and heredocs, “\u{“ followed by an invalid sequence will now result in an error:
$str = "\u{xyz}"; // Fatal error: Invalid UTF-8 codepoint escape sequence
To avoid this the leading backslash should be escaped:
$str = "\\u{xyz}"; // Works fine
However, “\u” without a following { is unaffected. As such the following code won’t error and will work the same as before:
$str = "\u202e"; // Works fine
Error handling
Errors as throwables
There are now two exception classes: Exception and Error. Both classes implement a new interface Throwable. Type hints in exception handling code may need to be changed to account for this.
Fatal errors
Some fatal errors and recoverable fatal errors now throw an Error instead. As Error is a separate class from Exception, these exceptions will not be caught by existing try/catch blocks.
For the recoverable fatal errors which have been converted into an exception, it is no longer possible to silently ignore the error from an error handler. In particular, it is no longer possible to ignore type hint failures.
Parser errors
Parser errors now generate a ParseError that extends Error. Error handling for eval()s on potentially invalid code should be changed to catch ParseError in addition to the previous return value / error_get_last() based handling.
Internal class constructor failures
Constructors of internal classes will now always throw an exception on failure. Previously some constructors returned NULL or an unusable object.
Reclassification of E_STRICT
The error level of some E_STRICT notices has been changed.
Other language changes
Static calls to non-static methods
Removed support for static calls to non-static methods from an incompatible
$this context. In this case $this will not be defined, but the call will be
allowed with a deprecation notice. An example:
class A {
public function test() { var_dump($this); }
}
// Note: Does NOT extend A
class B {
public function callNonStaticMethodOfA() { A::test(); }
}
(new B)->callNonStaticMethodOfA();
// Deprecated: Non-static method A::test() should not be called statically
// Notice: Undefined variable $this
NULL
Note that this only applies to calls from an incompatible context. If class B extended from A the call would be allowed without any notices.
Reserved words
It is no longer possible to use the following class, interface and trait names (case-insensitive):
bool
int
float
string
null
false
true
This applies to class/interface/trait declarations, class_alias() and use statements.
Furthermore the following class, interface and trait names are now reserved for future use, but do not yet throw an error when used:
resource
object
mixed
numeric
Parenthesis requirement for yield
The yield language construct no longer requires parentheses when used in an expression context. It is now a right-associative operator with precedence between the “print” and “⇒” operators. This can result in different behavior in some cases, for example:
echo yield -1;
// Was previously interpreted as
echo (yield) - 1;
// And is now interpreted as
echo yield (-1);
yield $foo or die;
// Was previously interpreted as
yield ($foo or die);
// And is now interpreted as
(yield $foo) or die;
Such cases can always be resolved by adding additional parentheses.
Other removals
- Removed ASP (
<%) and script (<script language=php>) tags. (RFC: https://wiki.php.net/rfc/remove_alternative_php_tags) - Removed support for assigning the result of new by reference.
- Removed support for scoped calls to non-static methods from an incompatible
$thiscontext. See details in https://wiki.php.net/rfc/incompat_ctx. - Removed support for
#-style comments in ini files. Use;-style comments instead. -
$HTTP_RAW_POST_DATAis no longer available. Use thephp://inputstream instead.
Standard Library Changes
-
substr()now returns an empty string instead ofFALSEwhen the truncation happens on boundaries. -
call_user_method()andcall_user_method_array()no longer exists. -
ob_start()no longer issues anE_ERROR, but instead anE_RECOVERABLE_ERRORin case an output buffer is created in an output buffer handler. - The internal sorting algorithm has been improved, what may result in different sort order of elements that compare as equal.
- Removed
dl()function on fpm-fcgi. -
setcookie()with an empty cookie name now issues anE_WARNINGand doesn’t send an empty set-cookie header line anymore.
Other Changes
Curl
- Removed support for disabling the CURLOPT_SAFE_UPLOAD option. All curl file uploads must use the curl_file / CURLFile APIs.
Date
- Removed $is_dst parameter from mktime() and gmmktime().
DBA
- dba_delete() now returns false if the key was not found for the inifile handler, too.
GMP
- Requires libgmp version 4.2 or newer now.
- gmp_setbit() and gmp_clrbit() now return FALSE for negative indices, making them consistent with other GMP functions.
Intl
- Removed deprecated aliases datefmt_set_timezone_id() and IntlDateFormatter::setTimeZoneID(). Use datefmt_set_timezone() and IntlDateFormatter::setTimeZone() instead.
libxml
- Added LIBXML_BIGLINES parser option. It’s available starting with libxml 2.9.0 and adds suppport for line numbers >16-bit in the error reporting.
Mcrypt
- Removed deprecated mcrypt_generic_end() alias in favor of mcrypt_generic_deinit().
- Removed deprecated mcrypt_ecb(), mcrypt_cbc(), mcrypt_cfb() and mcrypt_ofb() functions in favor of mcrypt_encrypt() and mcrypt_decrypt() with an MCRYPT_MODE_* flag.
Session
- session_start() accepts all INI settings as array. e.g. [‘cache_limiter’⇒‘private’] sets session.cache_limiter=private. It also supports ‘read_and_close’ which closes session data immediately after read data.
- Save handler accepts validate_sid(), update_timestamp() which validates session ID existence, updates timestamp of session data. Compatibility of old user defined save handler is retained.
- SessionUpdateTimestampHandlerInterface is added. validateSid(), updateTimestamp() is defined in the interface.
- session.lazy_write(default=On) INI setting enables only write session data when session data is updated.
Opcache
- Removed opcache.load_comments configuration directive. Now doc comments loading costs nothing and always enabled.
OpenSSL
- Removed the “rsa_key_size” SSL context option in favor of automatically setting the appropriate size given the negotiated crypto algorithm.
- Removed “CN_match” and “SNI_server_name” SSL context options. Use automatic detection or the “peer_name” option instead.
PCRE:
- Removed support for /e (PREG_REPLACE_EVAL) modifier. Use preg_replace_callback() instead.
PDO_pgsql:
- Removed PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT attribute in favor of ATTR_EMULATE_PREPARES.
Standard:
- Removed string category support in setlocale(). Use the LC_* constants instead.
- Removed set_magic_quotes_runtime() and its alias magic_quotes_runtime().
JSON:
- Rejected RFC 7159 incompatible number formats in json_decode string - top level (07, 0xff, .1, -.1) and all levels ([1.], [1.e1])
- Calling json_decode with 1st argument equal to empty PHP string or value that after casting to string is empty string (NULL, FALSE) results in JSON syntax error.
Stream:
- Removed set_socket_blocking() in favor of its alias stream_set_blocking().
XSL:
- Removed xsl.security_prefs ini option. Use XsltProcessor::setSecurityPrefs() instead.