web

php7 新特性使用总结

2015年12月3日,PHP 开发小组宣布第一个可用的PHP 7.0版本诞生了!这一天,标志着PHP版本进入了7系列:

http://php.net/archive/2015.php#id2015-12-03-1

PHP 7搭载了新版Zend引擎,做了大量的改善和提升,并提供了很多新特性,比如:
1. 速度的提升,PHP 7的速度是PHP 5.6的两倍以上。
2. 内存使用显著优化。
3. 使用抽象语法树。
4. 支持64位,统一不同平台下的整型长度,字符串和文件上传都支持大于2GB。
5. 更多Error错误可以进行异常处理。
6. 更安全可靠的随机数生成。
7. 移除了旧的和不支持的 SAPIs 和扩展
8. 等等

对于PHP 7的新特性,由于改动很多,这里不一一做详细介绍,有兴趣可以直接去官网访问:
http://php.net/manual/en/migration70.new-features.php

下面是在使用中,觉得很有帮助的新特性,我在此作汇总。

  • 标量类型声明
  • 4种标量类型声明:int,float,string,bool
    2种模式:强制 (默认) 和 严格模式

    默认情况下,所有的PHP文件都处于弱类型校验模式。新的declare指令,通过指定strict_types的值(1或者0),1表示严格类型校验模式,作用于函数调用和返回语句;0表示弱类型校验模式。declare(strict_types=1)必须是文件的第一个语句。如果这个语句出现在文件的其他地方,将会产生一个编译错误,块模式是被明确禁止的。


    strict_types指令只影响指定使用的文件,不会影响被它包含(通过include等方式)进来的其他文件。

    举例,在默认情况下(strict_types=0):

    declare(strict_types=0);
    function foo(int $val)
    {
        echo $val;
    }
    foo(0);       //输出0
    foo('1');     //输出1
    foo('test');  //报错

    此时,最后一个会报错,提示类型不对:
    Fatal error: Uncaught TypeError: Argument 1 passed to foo() must be of the type integer, string given.

    我们修改参数,在严格情况下(strict_types=1):

    declare(strict_types=1);
    function foo(int $val)
    {
        echo $val;
    }
    foo(0);       //输出0
    foo('1');     //报错
    foo('test');  //报错

    我们发现,最后两个都会报错,提示类型不对:
    Fatal error: Uncaught TypeError: Argument 1 passed to foo() must be of the type integer, string given.

    也就是说,默认情况下,会将字符串1转换为int 1,而严格模式下,则不会转换。

    在使用过程中,哪个文件需要严格模式,就在哪个文件头部声明:


    strict_types指令影响同一个文件下的所有函数调用,不管这个被调函数是否在这个文件内定义的,都会采用严格类型校验模式。

    在现代编程语言的实际应用中,有三种主要的方法去检查参数和返回值的类型:
    1. 全严格类型检查(也就是不会有类型转换发生)。例如F#、GO、Haskell、Rust和Facebook的Hack的用法。
    2. 广泛原始类型检查(“安全”的类型转换会发生)。例如Java、D和Pascal。他们允许广泛原始类型转换(隐式转换),也就是说,一个8-bit的integer可以根据函数参数需要,被隐形转换为一个16-bit的integer,而且int也可以被转换为float的浮点数。其他类型的隐式转换则不被允许。
    3. 弱类型检查(允许所有类型转换,可能会引起警告),它被有限制地使用在C、C#、C++和Visual Basic中。它们尝试尽可能“不失败”,完成一次转换。

    通过上面的例子,不难看出,PHP 7默认采用弱类型校验机制,同时追加一个开关,允许转换为广泛类型校验机制(也就是严格类型校验机制)。

    为什么要在每个文件里各自声明strict_types校验模式,而不是统一声明,或者直接在函数声明。其实有很多好处:
    1. 人们可以选择适合他们的类型校验,也就是说,这个方案希望同时满足严格和弱类型校验两个阵营。
    2. API不会被强制适应某个类型声明模式。
    3. 因为文件默认使用弱类型校验方案,已经存在的代码库,可以在不破坏代码结构的情况下,添加标量类型声明。也可以让代码库逐步添加类型声明,或者仅部分模块添加。
    4. 只需要一个单一语法,就可以定义标量类型声明。
    5. 更喜欢严格类型校验的人,通常,不仅将这个特性使用在用户定义的函数,同时也使用在拓展和PHP内置函数中。也就是说,PHP使用者会得到一个统一机制,而不会产生严格标量声明的矛盾。
    6. 在严格类型校验模式下,拓展和PHP内置函数产生的类型校验失败的错误级别,和用户自定函数产生的会保持一致,都是E_RECOVERABLE_ERROR。
    7. 它允许严格类型和弱类型代码,在一个单一的代码库中无缝集成。

    具体参考:

    https://wiki.php.net/rfc/scalar_type_hints_v5

  • 返回值类型声明
  • 函数的返回值支持类型声明,和标量类型声明类似,也会根据strict_types而采用相应的模式。

    比如,这个代码会返回3:

    declare(strict_types=0);
    function getNum(): int
    {
        return 3.14;
    }
    var_dump(getNum());

    如果改为严格模式:

    declare(strict_types=1);
    function getNum(): int
    {
        return 3.14;
    }
    var_dump(getNum());    //int(3)

    报错:Uncaught TypeError: Return value of getNum() must be of the type integer, float returned

  • null合并运算符
  • 由于日常使用中存在大量同时使用三元表达式和 isset()的情况, 我们添加了null合并运算符 (??) 这个语法糖。如果变量存在且值不为NULL, 它就会返回自身的值,否则返回它的第二个操作数。


    语法糖是指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。

    我们在开发web应用时,经常会读取GET和POST参数,为了更好的兼容,有些参数如果不传则使用默认值,代码可能如下:

    $limit = isset($_POST['limit']) ? $_POST["limit"] : 10;
    $username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

    在PHP 7中,我们可以使用一种更为简洁的方式:

    $limit = $_POST['limit'] ?? 10;
    $username = $_GET['user'] ?? 'nobody';

    更一般的,可以对GET和POST都做兼容处理:

    $username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';

    对于三元表达式,这里强调一点,三元表达式和null合并运算符不一样,如果变量是0,两者还是有区别的:

    $a = false;
    var_dump($a ?? 3);    // bool(false)
    var_dump($a ?: 3);    // int(3)

    null合并运算符,仅判断是否存在,是否为null,因此0或者false都被视为存在。
  • 太空船操作符
  • Spaceship operator,也叫太空船操作符,主要是长得像一艘船:
    <=>

    太空船操作符用于比较两个表达式,当$a小于、等于或大于$b时它分别返回-1、0或1。

    很好理解,直接看官方代码:

    <?php
    // 整数
    echo 1 <=> 1; // 0
    echo 1 <=> 2; // -1
    echo 2 <=> 1; // 1

    // 浮点数
    echo 1.5 <=> 1.5; // 0
    echo 1.5 <=> 2.5; // -1
    echo 2.5 <=> 1.5; // 1
     
    // 字符串
    echo "a" <=> "a"; // 0
    echo "a" <=> "b"; // -1
    echo "b" <=> "a"; // 1

    对于这一操作符,我问过几个人,他们都知道这个符号是什么,做什么,但是问他们应用场景的时候,一般都不知道,或者说可能用于排序,也不清楚什么样的排序场景会用到。

    其实,太空船主要是和php排序方法结合使用,可以在一些场景下更简洁优雅,比如usort、uasort、uksort等:

    $things = [
        [
            'foo' => 5.5,
            'bar' => 'abc'
        ],
        [
            'foo' => 7.7,
            'bar' => 'xyz'
        ],
        [
            'foo' => 2.2,
            'bar' => 'efg'
        ]
    ];

    // Sort $things by 'foo' property, ascending
    usort($things, function ($a, $b) {
        return $a['foo'] <=> $b['foo'];
    });
    print_r($things);

    // Sort $things by 'bar' property, descending
    usort($things, function ($a, $b) {
        return $b['bar'] <=> $a['bar'];
    });
    print_r($things);

    代码会分别以升序和降序进行排序,输出如下:

    Array
    (
        [0] => Array
            (
                [foo] => 2.2
                [bar] => efg
            )
        [1] => Array
            (
                [foo] => 5.5
                [bar] => abc
            )
        [2] => Array
            (
                [foo] => 7.7
                [bar] => xyz
            )
    )

    Array
    (
        [0] => Array
            (
                [foo] => 7.7
                [bar] => xyz
            )
        [1] => Array
            (
                [foo] => 2.2
                [bar] => efg
            )
        [2] => Array
            (
                [foo] => 5.5
                [bar] => abc
            )
    )
  • 安全机制
  • 对于随机数的生成,PHP 7加入了高安全级别的随机字符串和随机整数两个函数:
    string random_bytes ( int $length )
    int random_int ( int $min , int $max )

    这两个函数,会借助系统内置的高安全级别随机:
    Windows系统使用CryptGenRandom。
    Linux系统使用getrandom(2) syscall。
    在其他平台会使用/dev/urandom。
    如果以上平台找不到对应的内置方法,则进行报错处理。

    对于加密要求不高的随机数,random_int和rand没什么区别,都是随即返回指定区间的随机数,stackoverflow上面有讨论,可以参考:

    PHP rand() vs. random_int()

    代码效果如下:

    //生成32位的16进制随机数
    $bytes = random_bytes(16);
    var_dump(bin2hex($bytes));
    //输出:string(32) "bfedab7de544bf5ea50ec9fc1fb03959"

    //随机生成四位校验码
    random_int(1000, 9999);
  • 断言/预期
  • assert() 方法是向后兼用并增强之前的 assert() 的方法,它使得在生产环境中启用断言为零成本,并且提供当断言失败时抛出特定异常的能力。老版本的API出于兼容目的将继续被维护,assert()现在是一个语言结构,它允许第一个参数是一个表达式,而不仅仅是一个待计算的string或一个待测试的boolean。

    在PHP 7中,assert()是一种语言结构,允许对期望进行定义:在开发和测试环境中生效的断言,但在生产过程中被优化为零成本。

    为了向后兼容,虽然assert_options()仍然可以用于行为控制,但是PHP 7提供了两个新的配置指令来控制其行为,而不是调用assert_options方法。

    指令 默认值 描述
    zend.assertions 1 1: 生成并执行代码 (开发环境)
    0: 生成代码但跳过执行
    -1: 不生成代码 (生产环境)
    assert.exception 0 1: 当断言失败时抛出指定的异常,在没有提供异常的情况下抛出一个新的AssertionError对象
    0: 不会触发异常,仅发出警告(和PHP 5兼容)

    1. 安装php时,php.ini-development和php.ini-production文件中,zend.assertions的值分别是1和-1。
    2. 关于assert的配置指令,直接在php.ini文件修改,ini_set不生效。

    官方提供的代码示例,比较清晰解释了指令的具体作用,如下。

    assert(true == false);
    echo 'Hi!';

    zend.assertions=0时,输出如下:

    Hi!

    zend.assertions=1,assert.exception=0时,输出如下:

    Warning: assert(): assert(true == false) failed in - on line 2
    Hi!

    zend.assertions=1,assert.exception=1时,输出如下:

    Fatal error: Uncaught AssertionError: assert(true == false) in -:2
    Stack trace:
    #0 -(2): assert(false, 'assert(true == ...')
    #1 {main}
     thrown in - on line 2
  • 其它特性
  • 针对PHP语法编写,进行了一些优化,让我们的代码更加美观。

    通过 define() 定义常量数组:

    define('ANIMALS', [
        'dog',
        'cat',
        'bird'
    ]);
    echo ANIMALS[1]; // outputs "cat"

    匿名类的支持:

    // PHP 5.x
    class MyLogger {
      public function log($msg) {
        print_r($msg . "\n");
      }
    }
     
    $pusher->setLogger( new MyLogger() );
     
    // New Hotness
    $pusher->setLogger(new class {
      public function log($msg) {
        print_r($msg . "\n");
      }
    });

    use的一次性导入:

    // PHP 7 之前的代码
    use some\namespace\ClassA;
    use some\namespace\ClassB;
    use some\namespace\ClassC as C;

    use function some\namespace\fn_a;
    use function some\namespace\fn_b;
    use function some\namespace\fn_c;

    use const some\namespace\ConstA;
    use const some\namespace\ConstB;
    use const some\namespace\ConstC;

    // PHP 7+ 及更高版本的代码
    use some\namespace\{ClassA, ClassB, ClassC as C};
    use function some\namespace\{fn_a, fn_b, fn_c};
    use const some\namespace\{ConstA, ConstB, ConstC};

    session_start() 可以接受一个 array 作为参数, 用来覆盖 php.ini 文件中设置的 会话配置选项:

    session_start([
        'cache_limiter' => 'private',
        'read_and_close' => true,
    ]);

    最后,附上PHP 7性能对比图:

    2 Comments

    1. pupil

      shy总准备给cc升php7了嘛

      • ranshy

        明年应该会升级一批吧,有的项目出于稳定性考虑,可能暂时不动吧~

    Leave a Reply to 取消回复

    黑ICP备15001596号