不向后兼容的变更 ¶
错误和异常处理相关的变更 ¶
在 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_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 代码的方式被移除。 受到影响的标签有:
开标签 | 闭标签 |
---|---|
<% | %> |
<%= | %> |
<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.0
或 34
)。第二,如果使用科学计数法表示数值,e
前面必须不是点号(.)(例如,3.e3
必须写作 3.0e3
或 3e3
)。另外,空字符串不再被视作有效的 JSON 字符串。
在数值溢出的时候,内部函数将会失败 ¶
将浮点数转换为整数的时候,如果浮点数值太大,导致无法以整数表达的情况下,在之前的版本中,内部函数会直接将整数截断,并不会引发错误。在 PHP 7.0 中,如果发生这种情况,会引发 E_WARNING 错误,并且返回 null
。
自定义会话处理器的返回值修复 ¶
在自定义会话处理器中,如果函数的返回值不是 false
,也不是 -1
,会引发致命错误。现在,如果这些函数的返回值不是布尔值,也不是 -1
或者 0
,函数调用结果将被视为失败,并且引发 E_WARNING 错误。
相等的元素在排序时的顺序问题 ¶
由于内部排序算法进行了提升,可能会导致对比时被视为相等的多个元素之间的顺序不稳定。
注意:
在对比时被视为相等的多个元素之间的排序顺序是不可信赖的,即使是相等的两个元素,他们的位置也可能被排序算法所改变。
错误的使用 break 和 continue 语句 ¶
在循环或者 switch
语句之外使用 break
和 continue
被视为编译型错误(之前视为运行时错误),会引发 E_COMPILE_ERROR
错误。
Mhash 不再是一个单独的扩展了 ¶
Mhash 扩展已经被完全整合进 Hash 扩展了。因此,不要再使用 extension_loaded() 函数来检测是否支持 MHash 扩展了,建议使用 function_exists() 函数来进行检测。另外,函数 get_loaded_extensions() 以及相关的特性中,也不再报告和 MHash 扩展相关的信息了。
declare(ticks) ¶
declare(ticks) 指示符不再泄漏到不同的编译单元中。