不向后兼容的变更

错误和异常处理相关的变更

在 PHP 7 中,很多致命错误以及可恢复的致命错误,都被转换为异常来处理了。这些异常继承自 Error 类,此类实现了 Throwable 接口(所有异常都实现了这个基础接口)。

这也意味着,当发生错误的时候,以前代码中的一些错误处理的代码将无法被触发。因为在 PHP 7 版本中,已经使用抛出异常的错误处理机制了。(如果代码中没有捕获 Error 异常,那么会引发致命错误)。

PHP 7 中的错误处理的更完整的描述,请参见 PHP 7 错误处理。本迁移指导主要是列出对兼容性有影响的变更。

set_exception_handler() 不再保证收到的一定是 Exception 对象

抛出 Error 对象时,如果 set_exception_handler() 里的异常处理代码声明了类型 Exception ,将会导致 fatal error。

想要异常处理器同时支持 PHP 5 和 7,应该删掉异常处理器里的类型声明。如果代码仅仅是升级到 PHP 7,则可以把类型 Exception 替换成 Throwable

<?php
// PHP 5 时代的代码将会出现问题
function handler(Exception $e) { ... }
set_exception_handler('handler');

// 兼容 PHP 5 和 7
function handler($e) { ... }

// 仅支持 PHP 7
function handler(Throwable $e) { ... }
?>

当内部构造方法失败的时候,始终抛出异常

在之前版本中,如果内部类的构造方法出错,会返回 null 或者一个不可用的对象。从 PHP 7 开始,如果内部类构造方法发生错误,那么会抛出异常。

解析错误会抛出 ParseError 异常

解析错误会抛出 ParseError 异常。对于 eval() 函数,需要将其包含到一个 catch 代码块中来处理解析错误。

E_STRICT 警告级别变更

原有的 E_STRICT 警告都被迁移到其他级别。E_STRICT 常量会被保留,所以调用 error_reporting(E_ALL|E_STRICT) 不会引发错误。

E_STRICT 警告级别变更
场景 新的级别/行为
将资源类型的变量用作键来进行索引 E_NOTICE
抽象静态方法 不再警告,会引发错误
重复定义构造器函数 不再警告,会引发错误
在继承的时候,方法签名不匹配 E_WARNING
在两个 trait 中包含相同的(兼容的)属性 不再警告,会引发错误
以非静态调用的方式访问静态属性 E_NOTICE
变量应该以引用的方式赋值 E_NOTICE
变量应该以引用的方式传递(到函数参数中) E_NOTICE
以静态方式调用实例方法 E_DEPRECATED

关于变量处理的变化

PHP 7 现在使用了抽象语法树来解析源代码。这使得许多由于之前的 PHP 的解释器的限制所不可能实现的改进得以实现。但出于一致性的原因导致了一些特殊例子的变动,而这些变动打破了向后兼容。在这一章中将详细介绍这些例子。

关于间接使用变量、属性和方法的变化

对变量、属性和方法的间接调用现在将严格遵循从左到右的顺序来解析,而不是之前的混杂着几个特殊案例的情况。下面这张表说明了这个解析顺序的变化。

间接调用的表达式的新旧解析顺序
表达式 PHP 5 的解析方式 PHP 7 的解析方式
$$foo['bar']['baz'] ${$foo['bar']['baz']} ($$foo)['bar']['baz']
$foo->$bar['baz'] $foo->{$bar['baz']} ($foo->$bar)['baz']
$foo->$bar['baz']() $foo->{$bar['baz']}() ($foo->$bar)['baz']()
Foo::$bar['baz']() Foo::{$bar['baz']}() (Foo::$bar)['baz']()

使用了旧的从右到左的解析顺序的代码必须被重写,明确的使用大括号来表明顺序(参见上表中间一列)。这样使得代码既保持了与 PHP 7.x 的前向兼容性,又保持了与 PHP 5.x 的后向兼容性。

这同样影响了 global 关键字。如果需要的话可以使用大括号来模拟之前的行为。

<?php
function f() {
// Valid in PHP 5 only.
global $$foo->bar;

// Valid in PHP 5 and 7.
global ${$foo->bar};
}
?>

关于 list() 处理方式的变更

list() 不再以反向的顺序来进行赋值

list() 现在会按照变量定义的顺序来给他们进行赋值,而非反过来的顺序。 通常来说,这只会影响 list() 与数组的 [] 操作符一起使用的案例,如下所示:

<?php
list($a[], $a[], $a[]) = [1, 2, 3];
var_dump($a);
?>

以上示例在 PHP 5 中的输出:

array(3) {
  [0]=>
  int(3)
  [1]=>
  int(2)
  [2]=>
  int(1)
}

以上示例在 PHP 7 中的输出:

array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}

总之,我们推荐不要依赖 list() 的赋值顺序,因为这是在未来也许会变更的实现细节。

空的 list() 赋值支持已经被移除

list() 结构现在不再能是空的。如下的例子不再被允许:

<?php
list() = $a;
list(,,) =
$a;
list(
$x, list(), $y) = $a;
?>
list() 不再能解开 string

list() 不再能解开字符串(string)变量。可以使用 str_split() 来代替。

当元素在引用赋值期间自动创建元素时,数组的顺序会发生改变。

当通过引用赋值自动创建这些元素时,数组中元素的顺序就会发生变化。例如:

<?php
$array
= [];
$array["a"] =& $array["b"];
$array["b"] = 1;
var_dump($array);
?>

以上示例在 PHP 5 中的输出:

array(2) {
  ["b"]=>
  &int(1)
  ["a"]=>
  &int(1)
}

以上示例在 PHP 7 中的输出:

array(2) {
  ["a"]=>
  &int(1)
  ["b"]=>
  &int(1)
}

函数参数附近的括号不再影响行为

在 PHP 5中,在以引用方式传递函数参数时,使用冗余的括号对可以隐匿严格标准的警告。现在,这个警告总会触发。

<?php
function getArray() {
return [
1, 2, 3];
}

function
squareArray(array &$a) {
foreach (
$a as &$v) {
$v **= 2;
}
}

// Generates a warning in PHP 7.
squareArray((getArray()));
?>

以上示例会输出:

Notice: Only variables should be passed by reference in /tmp/test.php on line 13

foreach 的变化

foreach 发生了细微的变化,控制结构,主要围绕阵列的内部数组指针和迭代处理的修改。

foreach 不再改变内部数组指针

在 PHP 7 之前,当数组通过 foreach 迭代时,数组指针会移动。现在开始,不再如此,见下面代码

<?php
$array
= [0, 1, 2];
foreach (
$array as &$val) {
var_dump(current($array));
}
?>

以上示例在 PHP 5 中的输出:

int(1)
int(2)
bool(false)

以上示例在 PHP 7 中的输出:

int(0)
int(0)
int(0)

foreach 通过值遍历时,操作的值为数组的副本

当默认使用通过值遍历数组时,foreach 实际操作的是数组的迭代副本,而非数组本身。这就意味着迭代期间对数组所做的修改不会影响迭代的值。

foreach 通过引用遍历时,有更好的迭代特性

当使用引用遍历数组时,现在 foreach 在迭代中能更好的跟踪变化。例如,在迭代中添加一个迭代值到数组中,参考下面的代码:

<?php
$array
= [0];
foreach (
$array as &$val) {
var_dump($val);
$array[1] = 1;
}
?>

以上示例在 PHP 5 中的输出:

int(0)

以上示例在 PHP 7 中的输出:

int(0)
int(1)

Traversable 对象的遍历

迭代一个非 Traversable 对象将会与迭代一个引用数组的行为相同。这将导致在对象添加或删除属性时,foreach 通过引用遍历时,有更好的迭代特性也能被应用

int 处理更改

无效的八进制字符(Invalid octal literals)

在之前,一个八进制字符如果含有无效数字,该无效数字将被静默删节(0128 将被解析为 012)。现在这样的八进制字符将产生解析错误。

负位移运算(Negative bitshifts)

以负数形式进行的位移运算将会抛出一个 ArithmeticError

<?php
var_dump
(1 >> -1);
?>

以上示例在 PHP 5 中的输出:

int(0)

以上示例在 PHP 7 中的输出:

Fatal error: Uncaught ArithmeticError: Bit shift by negative number in /tmp/test.php:2
Stack trace:
#0 {main}
  thrown in /tmp/test.php on line 2

超范围后产生位移

超出 int 位宽的位移操作(无论哪个方向)将始终得到 0 作为结果。在从前,这一操作是结构依赖的。

除以零的变化

之前的版本中,当 0 被做为除数时,无论是除数(/)或取模(%)操作,都会抛出一个 E_WARNING 错误并返回 false。现在,除法运算符(/)会返回一个由 IEEE 754 指定的浮点数:+INF, -INF 或 NAN。取模操作符(%)则会抛出 DivisionByZeroError 异常,并且不再产生 E_WARNING 错误。

<?php
var_dump
(3/0);
var_dump(0/0);
var_dump(0%0);
?>

以上示例在 PHP 5 中的输出:

Warning: Division by zero in %s on line %d
bool(false)

Warning: Division by zero in %s on line %d
bool(false)

Warning: Division by zero in %s on line %d
bool(false)

以上示例在 PHP 7 中的输出:

Warning: Division by zero in %s on line %d
float(INF)

Warning: Division by zero in %s on line %d
float(NAN)

PHP Fatal error:  Uncaught DivisionByZeroError: Modulo by zero in %s line %d

string 处理上的调整

十六进制字符串不再被认为是数字

含十六进制字符串不再被认为是数字。例如:

<?php
var_dump
("0x123" == "291");
var_dump(is_numeric("0x123"));
var_dump("0xe" + "0x1");
var_dump(substr("foo", "0x1"));
?>

以上示例在 PHP 5 中的输出:

bool(true)
bool(true)
int(15)
string(2) "oo"

以上示例在 PHP 7 中的输出:

bool(false)
bool(false)
int(0)

Notice: A non well formed numeric value encountered in /tmp/test.php on line 5
string(3) "foo"

filter_var() 函数可以用于检查 string 是否含有十六进制数字,并将其转换为 int

<?php
$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)
?>

\u{ 可能引起错误

由于新的 Unicode codepoint 转义语法,紧连着无效序列并包含 \u{ 的字串可能引起致命错误。为了避免这一报错,应该避免反斜线开头。

移除的函数

call_user_method()call_user_method_array()

这两个函数从 PHP 4.1.0 开始被废弃,应该使用 call_user_func()call_user_func_array()。也可以考虑使用变量函数或者 ... 操作符。

所有的 ereg* 函数

所有 ereg 系列函数被删掉了。PCRE 作为推荐的替代品。

mcrypt 别名

已废弃的 mcrypt_generic_end() 函数已被移除,请使用 mcrypt_generic_deinit() 代替。

此外,已废弃的 mcrypt_ecb()mcrypt_cbc()mcrypt_cfb()mcrypt_ofb() 函数已被移除,请配合恰当的 MCRYPT_MODE_* 常量来使用 mcrypt_decrypt() 进行代替。

所有 ext/mysql 函数

所有 ext/mysql 函数已被删掉了。如何选择不同的 MySQL API,详情请见选择 MySQL API

所有 ext/mssql 函数

所有 ext/mssql 函数已被移除。

intl 别名

已废弃的 datefmt_set_timezone_id()IntlDateFormatter::setTimeZoneID() 函数已被移除,请使用 datefmt_set_timezone()IntlDateFormatter::setTimeZone() 代替。

set_magic_quotes_runtime()

移除了 set_magic_quotes_runtime() 和它的别名 magic_quotes_runtime()。它们在 PHP 5.3.0 中已经被废弃,并由于 PHP 5.4.0 移除魔术引号(Magic Quotes)而没有用处。

set_socket_blocking()

已废弃的 set_socket_blocking() 函数已被移除,请使用 stream_set_blocking() 代替。

PHP-FPM 中的 dl()

dl() 在 PHP-FPM 不再可用,在 CLI 和 embed SAPI 中仍可用。

GD Type1 函数

GD 扩展中移除了对 PostScript Type1 字体的支持,从而移除了以下函数:

  • imagepsbbox()
  • imagepsencodefont()
  • imagepsextendfont()
  • imagepsfreefont()
  • imagepsloadfont()
  • imagepsslantfont()
  • imagepstext()

推荐使用 TrueType 字体和相关的函数作为替代。

移除掉的 INI 配置指令

被移除的功能

以下 INI 配置指令已经被移除,同时移除的还有其对应的功能

  • always_populate_raw_post_data
  • asp_tags

xsl.security_prefs

已移除 xsl.security_prefs 指令。取而代之的是,应该调用 XsltProcessor::setSecurityPrefs() 方法来控制预处理器的基础安全首选项。

其他向后兼容相关的变更

new 操作符创建的对象不能以引用方式赋值给变量

new 语句创建的对象不能以引用的方式赋值给变量。

<?php
class C {}
$c =& new C;
?>

以上示例在 PHP 5 中的输出:

Deprecated: Assigning the return value of new by reference is deprecated in /tmp/test.php on line 3

以上示例在 PHP 7 中的输出:

Parse error: syntax error, unexpected 'new' (T_NEW) in /tmp/test.php on line 3

无效的类、接口以及 trait 命名

不能以下列名字来命名类、接口以及 trait:

此外,也不要使用下列的名字来命名类、接口以及 trait。虽然在 PHP 7.0 中,这并不会引发错误,但是这些名字是保留给将来使用的。

移除了 ASP 和 script PHP 标签

使用类似 ASP 的标签,以及 script 标签来区分 PHP 代码的方式被移除。 受到影响的标签有:

被移除的 ASP 和 script 标签
开标签 闭标签
<% %>
<%= %>
<script language="php"> </script>

从不匹配的上下文发起调用

在不匹配的上下文中以静态方式调用非静态方法,在 PHP 5.6 中已经废弃,但是在 PHP 7.0 中,会导致被调用方法中未定义 $this 变量,以及此行为已经废弃的警告。

<?php
class A {
public function
test() { var_dump($this); }
}

// 注意:并没有从类 A 继承
class B {
public function
callNonStaticMethodOfA() { A::test(); }
}

(new
B)->callNonStaticMethodOfA();
?>

以上示例在 PHP 5.6 中的输出:

Deprecated: Non-static method A::test() should not be called statically, assuming $this from incompatible context in /tmp/test.php on line 8
object(B)#1 (0) {
}

以上示例在 PHP 7 中的输出:

Deprecated: Non-static method A::test() should not be called statically in /tmp/test.php on line 8

Notice: Undefined variable: this in /tmp/test.php on line 3
NULL

yield 变更为右联接运算符

在使用 yield 关键字的时候,不再需要括号,并且它变更为右联接操作符,其运算符优先级介于 print=> 之间。这可能导致现有代码的行为发生改变:

<?php
echo yield -1;
// 在之前版本中会被解释为:
echo (yield) - 1;
// 现在,它将被解释为:
echo yield (-1);

yield
$foo or die;
// 在之前版本中会被解释为:
yield ($foo or die);
// 现在,它将被解释为:
(yield $foo) or die;
?>

可以通过使用括号来消除歧义。

函数定义不可以包含多个同名参数

在函数定义中,不可以包含两个或多个同名的参数。例如,下面代码中的函数定义会触发 E_COMPILE_ERROR 错误:

<?php
function foo($a, $b, $unused, $unused) {
//
}
?>

Switch 语句不可以包含多个 default 块

在 switch 语句中,两个或者多个 default 块的代码已经不再被支持。例如,下面代码中的 switch 语句会触发 E_COMPILE_ERROR 错误:

<?php
switch (1) {
default:
break;
default:
break;
}
?>

在函数中检视参数值会返回当前的值

当在函数代码中使用 func_get_arg()func_get_args() 函数来检视参数值,或者使用 debug_backtrace() 函数查看回溯跟踪,以及在异常回溯中所报告的参数值是指参数当前的值(有可能是已经被函数内的代码改变过的值),而不再是参数被传入函数时候的原始值了。

<?php
function foo($x) {
$x++;
var_dump(func_get_arg(0));
}
foo(1);?>

以上示例在 PHP 5 中的输出:

1

以上示例在 PHP 7 中的输出:

2

$HTTP_RAW_POST_DATA 被移除

不再提供 $HTTP_RAW_POST_DATA 变量。请使用 php://input 作为替代。

INI 文件中 # 注释格式被移除

在 INI 文件中,不再支持以 # 开始的注释行,请使用 ;(分号)来表示注释。此变更适用于 php.ini 以及用 parse_ini_file()parse_ini_string() 函数来处理的文件。

JSON 扩展已经被 JSOND 取代

JSON 扩展已经被 JSOND 扩展取代。对于数值的处理,有以下两点需要注意的:第一,数值不能以点号(.)结束(例如,数值 34. 必须写作 34.034)。第二,如果使用科学计数法表示数值,e 前面必须不是点号(.)(例如,3.e3 必须写作 3.0e33e3)。另外,空字符串不再被视作有效的 JSON 字符串。

在数值溢出的时候,内部函数将会失败

将浮点数转换为整数的时候,如果浮点数值太大,导致无法以整数表达的情况下,在之前的版本中,内部函数会直接将整数截断,并不会引发错误。在 PHP 7.0 中,如果发生这种情况,会引发 E_WARNING 错误,并且返回 null

自定义会话处理器的返回值修复

在自定义会话处理器中,如果函数的返回值不是 false,也不是 -1,会引发致命错误。现在,如果这些函数的返回值不是布尔值,也不是 -1 或者 0,函数调用结果将被视为失败,并且引发 E_WARNING 错误。

相等的元素在排序时的顺序问题

由于内部排序算法进行了提升,可能会导致对比时被视为相等的多个元素之间的顺序不稳定。

注意:

在对比时被视为相等的多个元素之间的排序顺序是不可信赖的,即使是相等的两个元素,他们的位置也可能被排序算法所改变。

错误的使用 break 和 continue 语句

在循环或者 switch 语句之外使用 breakcontinue 被视为编译型错误(之前视为运行时错误),会引发 E_COMPILE_ERROR 错误。

Mhash 不再是一个单独的扩展了

Mhash 扩展已经被完全整合进 Hash 扩展了。因此,不要再使用 extension_loaded() 函数来检测是否支持 MHash 扩展了,建议使用 function_exists() 函数来进行检测。另外,函数 get_loaded_extensions() 以及相关的特性中,也不再报告和 MHash 扩展相关的信息了。

declare(ticks)

declare(ticks) 指示符不再泄漏到不同的编译单元中。

add a note add a note

User Contributed Notes 10 notes

up
184
me at fquff dot io
8 years ago
[Editor's note: fixed limit on user request]

As a mathematician, 3/0 == +INF IS JUST WRONG. You can't just assume 3/0 == lim_{x->0+} 3/x, which is +INF indeed, because division IS NOT A CONTINUOUS FUNCTION in x == 0.

Also, 3/0 == +INF ("positive" infinity) while -3/0 == -INF ("negative" infinity) requires the assumption that 0 is a positive number, which is just as illogical as it looks like.

The fact that a warning is emitted is good, but it should definitely equals to NaN. ±INF is just illogical (and arithmetically wrong).

Except for this "detail", looks an amazing update, can't wait to test it even further!

Cheers,
P.
up
102
mossy2100
8 years ago
Although $x/0 is technically not infinity in a purely mathematical sense, when you understand why the IEEE float includes a value for infinity, and returns infinity in this case, it makes sense that PHP would agree with this.

The reason is that programmers don't usually divide by 0 on purpose. A value of 0 as a divisor usually occurs due to underflow, i.e. a value which is too small to be represented as a float. So, for example, if you have values like:
$x = 1;
$y = 1e-15 * 1e-15;
$z = $x/$y;
Because $y will have underflowed to 0, the division operation will throw the division by zero warning, and $z will be set to INF. In a better computer, however, $y would not have the value 0 (it would be 1e-30) and $z would not have the value INF (it would be 1e30).

In other words, 0 is not only representative of an actual 0, but also a very small number which float cannot represent correctly (underflow), and INF does not only represent infinity, but also a very big number which float cannot represent correctly (overflow). We do the best we can within the limitations of floating point values, which are really just good approximations of the actual numbers being represented.

What does bother me is that division by zero is handled in two different ways depending on the operator. I would have preferred the new DivisionByZeroError exception to be thrown in all cases, for consistency and to enforce good programming practices.
up
126
tuxedobob
8 years ago
As a programmer, I don't care whether 3/0 is INF or NaN. Both answers are (probably) equally useless, and tell me that something somewhere else is screwed up.
up
16
tkondrashov at gmail dot com
6 years ago
split() was also removed in 7.0, so be sure to check your old code for it as well as the functions listed in this doc
up
10
Frank
8 years ago
[Editor's Note: that change is listed in the "Changed functions" section.]

The substr function has also been changed in a backward incompatible way.

<?php
substr
("test",4);  # false in PHP 5,  "" in PHP 7
?>

In fact, this is the only thing we had to change in a number of places for our code base to run on PHP 7. It's definitely an improvement though, as the old behavior tended to cause bugs in border cases.
up
5
maba at mb-systemhaus dot net
8 years ago
NOTE:
the new variable handling in PHP 7 also has side effects on the COM .NET extension. Numeric variants (in the Windows application space) now must be quoted when passed over from PHP. This is the case when the receiving application expects a variable of type variant.

Here is an example:

<?php
  $word
= new COM('Word.Application');

 
// now load a document, ...

  // the following works in PHP 5 but will throw an exception in PHP 7
 
$word->ActiveDocument->PrintOut(false, false, 0, $outfile);

 
// the following works in PHP 7 as well, please note the quotes around the '0'
 
$word->ActiveDocument->PrintOut(false, false, '0', $outfile);
?>
up
3
ilya dot chase at yandex dot ru
5 years ago
Take note that in preg_replace() function, flag '\e' was deleted in PHP 7.0.0. This function will return null always with this flag. Doc: https://www.php.net/manual/ru/function.preg-replace.php
up
-1
viktor dot csiky at nospam dot nospam dot eu
8 years ago
It is stated:

"foreach by-value operates on a copy of the array

When used in the default by-value mode, foreach will now operate on a copy of the array being iterated rather than the array itself. This means that changes to the array made during iteration will not affect the values that are iterated."

Please note that this is not exactly true. New foreach operates on a copy of the array, by-value or by-reference. It seems that in the latter case, the array copy is simply moved over (to) the original array before it is presumably destroyed.

As a consequence of this, you may not "dereference" an array containing values - e.g. for use with ReflectionMethod::invokeArgs() or the good ole' call_user_func().
Consider the snippet below:

<?php
function deref(Array $inputArray)
{
       
$retVal = [];
       
        foreach (
$inputArray as &$inputValue)
        {
           
$retVal[] = $inputValue;
        }

        return
$retVal;
}
?>

As of PHP 7.0, this *will no longer work*. You will get the usual suspect:

PHP Warning:  Parameter n to whatever() expected to be a reference, value given in baz.php on line x

You need to convert it to explicitly reference the original array:
<?php
function deref(Array $inputArray)
{
       
$retVal = [];
       
        foreach (
$inputArray as $inputKey => $inputValue)
        {
           
$retVal[$inputKey] = &$inputArray[$inputKey];
        }

        return
$retVal;
}
?>

PLEASE NOTE that this might have the unforeseen consequence of your code not working anymore in php versions less than 5.3 (that is, 5.2 and below).
up
-10
Ray.Paseur sometimes uses Gmail
7 years ago
In the section captioned "Changes to the handling of indirect variables, properties, and methods" there are parentheses used in the table directly beneath "PHP 7 interpretation." 

The parentheses are intended to show the evaluation order, but they are not part of the syntax, and should not be used in the variable definition or reference.  This juxtaposition confused one of my colleagues; hopefully this note will save someone else some time.

Examples of the correct curly-brace syntax is further down the page, in the section captioned "global only accepts simple variables."
To Top