本文大部分内容参照“Backward incompatible changes - Migrating from PHP 5.6.x to PHP 7.0.x”编写,主要是一些PHP7.x在实现上相较5.x的一些变化,给那些希望升级PHP 到7.x版本的童鞋参考。

##1. error 和 exception 的处理

###1.1 fatal error 转换成 exception

一些fatal errorPHP7被转换成exception。这些错误继承Error类并且实现了Throwable接口。(注:Throwable是一个新的接口,现在基础的Exception实现了这个接口)

也就是说,set_exception_handler()不一定只处理Exception,也可能是Error,以下是官网的一个🌰。

<?php
// PHP 5 era code that will break.
function handler(Exception $e) { ... }
set_exception_handler('handler');

// PHP 5 and 7 compatible.
function handler($e) { ... }

// PHP 7 only.
function handler(Throwable $e) { ... }

另外关于try catch的写法也需要注意:

try{
    ...
} catch(\Exception $e) {    //这里要注意是Exception or Error or Throwable
    ...
}

关于错误的详细描述:Errors in PHP7

###1.2 所有的E_STRICT错误都被重新归类到其他的错误级别,E_STRICT依然被保留。

<?php

error_reporting(E_ALL|E_STRICT);

class MyTest{
    function func(){
        echo "none static";
    }

}

//PHP 5.x : Strict Standards: Non-static method MyTest::func() ...
//PHP 7.X : Deprecated: Non-static method MyTest::func() ...
MyTest::func();

具体的分类参考下表:

Situation New level/behaviour
Indexing by a resource E_NOTICE
Abstract static methods Notice removed, triggers no error
“Redefining” a constructor Notice removed, triggers no error
Signature mismatch during inheritance E_WARNING
Same (compatible) property in two used traits Notice removed, triggers no error
Accessing static property non-statically E_NOTICE
Only variables should be assigned by reference E_NOTICE
Only variables should be passed by reference E_NOTICE
Calling non-static methods statically E_DEPRECATED

##2. 变量的处理

PHP7编译源文件时使用虚拟语法树(abstract syntax tree)。它能带来很多方面的改进,这些改进都是由于之前受制于老版本的编译器而无法实现的。详细如下:

###2.1 非直接访问变量、属性、方法

非直接访问变量,现在会严格按照从左到右的顺序解析,参考下表:

Expression PHP 5 interpretation PHP 7 interpretation
$$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’

###2.2 list()方法的变化

####2.2.1 list() 不在按倒序给变量赋值,而是按照正序赋值。

以下是官网的🌰:

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

PHP5输出:

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

PHP7输出:

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

当然,正常情况下不要依赖list()方法的赋值顺序,以防后续实现上再次发生变化。

####2.2.2 list()参数不可为空

以下情况均会产生错误:

<?php
    list() = $a;
    list(,,) = $a;
    list($x, list(), $y) = $a;

####2.2.3 list()不能用作字符串的切割

如果有类似的需求,使用str_split()代替。

<?php
    $str = 'abcde';
    list($a, $b) = $str;
    var_dump(array($a, $b));

PHP5输出:

array(2) {
    [0]=>
    string(1) "a"
    [1]=>
    string(1) "b"
}

PHP7输出:

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

###2.3 当元素被引用自动创建时的数组顺序发生改变的问题

以下是官网的🌰:

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

PHP5输出:

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

PHP7输出:

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

###2.4 global声明基本变量赋值

动态变量不能通过global声明。有使用的情况,可以通过带花括号的方式模拟:

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

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

要尽量避免,通过global去进行声明。

###2.5 通过(包裹function的参数会不起作用

当将一个方法作为引用参数时,在PHP7会抛出Notice错误,在PHP5中会抛出Strict错误。 当用(包裹function时,在PHP5中不会抛出任何错误,而在PHP7中同样还是会抛出Notice。 看🌰:

<?php

error_reporting(E_ALL|E_STRICT);

function param() {
    return array(1, 2, 3);
}

function test(&$a) {
    var_export($a);
}

//PHP5 : Strict Standards: Only variables should be passed by reference in...
//PHP7 : Notice: Only variables should be passed by reference in ...
test(param());

//PHP5 : NO ERROR
//PHP7 : Notice: Only variables should be passed by reference in ...
test((param()));

##3. foreach

###3.1 foreach不在改变数组的内部指针

数组的内部指针将不会随着foreach的迭代发生变化,下面是官网的🌰:

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

PHP5的输出:

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

PHP7的输出

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

###3.2 foreachby-valueby-reference

遍历值的模式下,foreach的操作是遍历数组的备份而不是数组本身,所以在遍历的过程中对数组的修改不会生效。 引用模式下,foreach会在遍历的过程中记录数组的变化并会即可生效,看下面的🌰:

<?php

$arr = array(
    'a' => 1,
);

foreach ($arr as $key => &$item) {
    echo $key."\n";
    $arr['b'] = 2;
}

unset($item);

PHP5输出:

a

PHP7输出:

a
b

##4. 整型

###4.1 非法8进制数字

PHP7中使用非法8进制数字,将会抛出Parse error,看下面🌰:

<?php

//PHP5 : 非法8进制数字会进行转换,$a = 01
//PHP7 : Parse error: Invalid numeric literal in ...
$a = 0187;

###4.2 负数移位

PHP7中数字移位如果是负数,会抛出ArithmeticError(注:ArithmeticError是PHP7中新加入的错误类型,表示计算型错误),下面是官网的一个🌰:

<?php
//PHP5 : echo int(0)
//PHP7 : Fatal error: Uncaught ArithmeticError: Bit shift by negative number in...
var_dump(1 >> -1);

###4.3 移位超出整数范围的情况

在PHP7中,如果移位超出范围,则都返回0,而之前是要依赖于具体的架构的。

###4.4 除 0 问题

PHP7中,如果0作为除数,则会抛出warning,并返回INFNAN;如果0作为余数会抛出DivisionByZeroError错误,下面的🌰:

<?php

//PHP5 : Warning: Division by zero in ..., return false
//PHP7 : Warning: Division by zero in ..., return NAN
echo (0/0)."\n";

//PHP5 : Warning: Division by zero in ..., return false
//PHP7 : Warning: Division by zero in ..., return INF
echo (3/0)."\n";

//PHP5 : Warning: Division by zero in ..., return false
//PHP7 : Fatal error: Uncaught DivisionByZeroError: Modulo by zero in ...
echo (0%0)."\n";

##5. 字符串

###5.1 16进制字符串不再当做数字处理

下面是官网的🌰:

<?php
    //PHP5 : bool(true)
    //PHP7 : bool(false)
    var_dump("0x123" == "291");
    //PHP5 : bool(true)
    //PHP7 : bool(false)
    var_dump(is_numeric("0x123"));
    //PHP5 : int(15)
    //PHP7 : int(0)
    var_dump("0xe" + "0x1");
    //PHP5 : string(2) "oo"
    //PHP7 : Notice: A non well formed numeric value encountered in ...
    var_dump(substr("foo", "0x1"));

###5.2 \u{ 引起的错误

PHP7引入的新特性Unicode codepoint escape syntax,可以支持unicode字符编码的展示。这样带来一个问题,如果原来使用的字符包含\u{会引发PHP报错,看下面的🌰:

<?php
echo "\u{9999}\n";
echo "\u{0009999}\n";
echo "\u{测试文本";

PHP5输出:

\u{9999}
\u{0009999}
\u{测试文本

PHP7:

Parse error: Invalid UTF-8 codepoint escape sequence in ...

如果要使用这种字符,需要将\u{前面的\转义,或者使用单引号。

##6 移除的方法

###6.1 call_user_method() 和 call_user_method_array()

移除方法 替换方法
call_user_method() call_user_func()
call_user_method_array() call_user_func_array()

###5.2 mcrypt()

移除方法 替换方法
mcrypt_generic_end() mcrypt_generic_deinit()
mcrypt_ecb(), mcrypt_cbc(), mcrypt_cfb(), mcrypt_ofb() mcrypt_decrypt()

###5.3 intl()

移除方法 替换方法
datefmt_set_timezone_id() IntlDateFormatter::setTimeZoneID()
datefmt_set_timezone() IntlDateFormatter::setTimeZone()

###6.4 set_magic_quotes_runtime()

移除set_magic_quotes_runtime(), magic_quotes_runtime()这两个方法,这两个方法之前主要是配合魔法引号使用的,在5.4版本中,已经将魔法引号废弃了。

###5.5 set_socket_blocking()

移除方法 替换方法
set_socket_blocking() stream_set_blocking()

###6.6 dl()

dl()用来运行时载入PHP扩展,在5.3版本时一些SAPI已经将其移除,PHP7中 PHP-FPM 中将其移除。目前CLI和嵌入式SAPI依然保留此功能。

###6.7 GD()

GD扩展不在支持PostScript Type1 font,所以以下相关的方法被删除:

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

建议使用新版的 TrueType 作为替换。

##7. ini 指令移除

  • always_populate_raw_post_data
  • asp_tags
  • xsl.security_prefs

##8. 其他不兼容项

###8.1 不能将new的实例的引用给变量赋值,下面是🌰:

<?php
class C {}

//PHP7 : Parse error: syntax error, unexpected 'new' (T_NEW) in
$c =& new C;

//It's OK
$a = new C;
$c =& $a;

###8.2 非法的class,interface,trait的命名

以下名字不能用作class,interface,trait的命名:

  • bool
  • int
  • float
  • string
  • NULL
  • TRUE
  • FALSE

以下名字暂时不会引发错误,但不建议使用:

  • resource
  • object
  • mixed
  • numeric

###8.3 ASP 和 PHP脚本标签移除

打开标签 关闭标签
<% %>
<%= %>
</script>

###8.4 上下文环境冲突

通过静态方式调用非静态方法,会抛出Deprecated,调用未定义的$this会抛出Notice,官网给出的🌰:

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

// Note: Does NOT extend A
class B {
    public function callNonStaticMethodOfA() { A::test(); }
}

(new B)->callNonStaticMethodOfA();

PHP5环境下:

Strict Standards: Non-static method A::test() should not be called statically, assuming $this from incompatible context in ...

PHP7环境下:

Deprecated: Non-static method A::test() should not be called statically in ...
Notice: Undefined variable: this in ...

###8.5 yield现在是一个右向运算符

yield 现在不需要使用(包裹,在PHP7中yield是一个右向运算符,优先级在print>=之间,以下为官网的🌰:

<?php
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;

###8.6 方法参数不能同名

以下代码会引发E_COMPILE_ERROR

<?php
//PHP7 : Fatal error: Redefinition of parameter $b in ...
function test($a, $b, $b) {

}

###8.7 switch 语句不能有多个default代码块

以下代码会引发E_COMPILE_ERROR

<?php

//PHP7 : Fatal error: Switch statements may only contain one default clause in ...
switch (1) {
    case 1:
    break;
    default:
    break;
    default:
    break;
}

###8.8 移除$HTTP_RAW_POST_DATA

使用php://input代替$HTTP_RAW_POST_DATA

###8.9 移除#符号在.ini文件中的注释

.ini不支持#表示注释,需要使用;代替,同时受影响的还有parse_ini_file()parse_ini_string()的实现。

###8.8 JSON扩展用JSOND代替

这项改变,主要带来两个影响:

  • 数字必须不能以.结尾
  • 科学计数法标识e的前面不能是.,例如:3.e3为非法的,必须用3.0e33e3

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

如果浮点数过大,在转换时无法以整数表示,会抛出warning,并返回null。之前的处理方式是将整数截断。

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

之前自定义session处理器中,如果函数返回的不是 false, 也不是 -1 会引发 fatal error,现在如果函数返回值不是 boolean, -1, 0,函数调用失败,引发 warning错误。

###8.11 func_get_arg() 和 func_get_args()返回当前参数值

在之前的版本中,func_get_arg() 和 func_get_args()返回最原始的方法参数,即使产生变化也会返回原始的数据,PHP7中返回当前最新数据,看下面🌰:

<?php

error_reporting(E_ALL|E_STRICT);

function a($a, $b){
    $a = 11;
    var_export(func_get_args());
}

a(12,12);

PHP5 输出:

array (
  0 => 12,
  1 => 12,
)

PHP7 输出:

array (
  0 => 11,
  1 => 12,
)

##总结: PHP7.x相较之前的版本的改变还是比较大的。个人看法是,如果是全新项目可以尝试,如果希望切换老版本需要格外谨慎,可以再观望一段时间,不建议马上迁移。

已经有很多web产品在生产环境中使用起来了,点击查看