跳转至

PHP 8.0.x#

说明

本文部分内容根据官网进行删减,仅整理常用特性。

1. 新特性#

1.1 命名参数#

PHP 8.0.0 开始引入了命名参数作为现有位置参数的扩展。命名参数允许根据参数名而不是参数位置向函数传参。这使得参数的含义自成体系,参数与顺序无关,并允许任意跳过默认值。

命名参数通过在参数名前加上冒号来传递。允许使用保留关键字作为参数名。参数名必须是一个标识符,不允许动态指定。

<?php
// 使用顺序传递参数:
array_fill(0, 100, 50);

// 使用命名参数:
array_fill(start_index: 0, count: 100, value: 50);

// 不支持动态指定
$s = 'start_index';
array_fill($s: 0, count: 100, value: 50);

// 命名参数也可以与位置参数相结合使用
htmlspecialchars($string, double_encode: false);
// 等价于
htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8', false);

1.2 注解(Attributes)#

内容较多,以下概述,详情可参考官网

注解功能提供了代码中的声明部分都可以添加结构化、机器可读的元数据的能力, 注解的目标可以是类、方法、函数、参数、属性、类常量。 通过 反射 API 可在运行时获取注解所定义的元数据。 因此注解可以成为直接嵌入代码的配置式语言。

1.3 构造器属性提升(Constructor Property Promotion)#

PHP 8.0.0 起,构造器的参数也可以相应提升为类的属性。

<?php
class Point {
    public function __construct(protected int $x, protected int $y = 0) {
    }
}

当构造器参数带访问控制(visibility modifier)时,PHP 会同时把它当作对象属性和构造器参数, 并赋值到属性。 构造器可以是空的,或者包含其他语句。 参数值赋值到相应属性后执行正文中额外的代码语句。

并非所有参数都需要提升。可以混合提升或不提升参数作为属性,也不需要按顺序。 提升后的参数不影响构造器内代码调用。

放在构造器提升参数里的属性会同时复制为属性和参数。

1.4 联合类型#

联合类型接受多个不同的简单类型做为参数。声明联合类型的语法为 T1|T2|...。联合类型自 PHP 8.0.0 起可用。

允许为空的联合类型

null 类型允许在联合类型中使用,例如 T1|T2|null 代表接受一个空值为参数。已经存在的 ?T 语法可以视为以下联合类型的一个简写 T|null

null 不能作为一个独立的类型使用。

false 伪类型

通过联合类型支持字面类型(Literal Type)false, 出于历史原因,很多内部函数在失败时返回了 false 而不是 null。 这类函数的典型例子是 strpos()

  • false 不能单独作为类型使用(包括可空 nullable 类型)。 因此,不可以用 falsefalse|null?false
  • true 字面类型不存在。

交集类型

只要能满足 class-type 的值,都可以在交集类型声明中使用,并且可使用多个值。 交集类型用 T1&T2&... 这样的语法指定。 交集类型从 PHP 8.1.0 可以使用

重复冗余的类型

为了能在复合类型声明中暴露简单的 bug,不需要加载 class 就可以在编译时让重复冗余的类型产生错误。 包含:

  • 解析出来的类型只能出现一次。例如这样的类型 int|string|INTCountable&Traversable&COUNTABLE 会导致错误。
  • 使用 mixed 会导致错误。
  • 对于联合类型:
  • 使用了 bool 时就不能再附带使用 false
  • 使用了 object 时就不能再附带使用 class 类型。
  • 使用了 iterable 时,arrayTraversable 都不能再附带使用。
  • 对于交集类型:
  • 使用 class-type 以外的类型会导致错误。
  • 使用 selfparentstatic 都会导致错误。

假设 A 和 B 都是一个类的别名, 而 A|B 仍然是有效的,哪怕它可以被简化为 A 或 B。 同样的,如果 B extends A {},那 A|B 仍然是有效的联合类型,尽管它可以被简化为 A

<?php
function foo(): int|INT {} // 不允许
function foo(): bool|false {} // 不允许
function foo(): int&Traversable {} // 不允许
function foo(): self&Traversable {} // 不允许

use A as B;
function foo(): A|B {} // 不允许 ("use" 是名称解析的一部分)
function foo(): A&B {} // 不允许 ("use" 是名称解析的一部分)

class_alias('X', 'Y');
function foo(): X|Y {} // 允许 (运行时才能知道重复性)
function foo(): X&Y {} // 允许 (运行时才能知道重复性)

1.5 Match 表达式#

match 表达式基于值的一致性进行分支计算。 match 表达式和 switch 语句类似, 都有一个表达式主体,可以和多个可选项进行比较。 与 switch 不同点是,它会像三元表达式一样求值。 与 switch 另一个不同点,它的比较是严格比较(===)而不是松散比较(==)。

语法:

<?php
$return_value = match (subject_expression) {
    single_conditional_expression => return_expression,
    conditional_expression1, conditional_expression2 => return_expression,
};

match 的基础用法:

<?php
$food = 'cake';

$return_value = match ($food) {
    'apple' => 'This food is an apple',
    'bar' => 'This food is a bar',
    'cake' => 'This food is a cake',  // string(19) "This food is a cake"
};

var_dump($return_value);

match 表达式必须使用分号 ; 结尾。

matchswitch 的区别:

  • match 比较分支值,使用了严格比较 (===), 而 switch 语句使用了松散比较。
  • match 表达式会返回一个值。
  • match 的分支不会像 switch 语句一样, 落空时执行下个 case
  • match 表达式必须彻底列举所有情况。

逐个检测匹配分支:

<?php
$result = match ($x) {
    foo() => ...,
    $this->bar() => ..., // 如果 foo() === $x,不会执行 $this->bar()
    $this->baz => beep(), // 只有 $x === $this->baz 时才会执行 beep() 
    // 等等
};

通过逗号分隔,包含多个表达式:

<?php
$result = match ($x) {
    // 匹配分支:
    $a, $b, $c => 5,
    // 等同于以下三个分支:
    $a => 5,
    $b => 5,
    $c => 5,
};

default 模式:

<?php
$expressionResult = match ($condition) {
    1, 2 => foo(),
    3, 4 => bar(),
    default => baz(),
};
?>

不能被任意分支条件处理, 会抛出 UnhandledMatchError

<?php
$condition = 5;

try {
    match ($condition) {
        1, 2 => foo(),
        3, 4 => bar(),
    };
} catch (\UnhandledMatchError $e) {
    var_dump($e);
}

多个 default 模式将会触发 E_FATAL_ERROR 错误。

处理非一致性检查:

<?php

$age = 23;

$result = match (true) {
    $age >= 65 => 'senior',
    $age >= 25 => 'adult',
    $age >= 18 => 'young adult',
    default => 'kid',
};

var_dump($result);  // string(11) "young adult"

个人补充:支持数组表达式:

<?php

[$a, $b] = [1, 2];

$result = match([$a, $b]) {
    [1, 2] => '1, 2',
    [3, 4] => '3, 4',
    [5, 6] => '5, 6',
};

print_r($result); // 1, 2

1.6 Nullsafe 运算符#

类属性和方法可以通过 nullsafe 操作符访问: ?->;此操作的结果,类似于在每次访问前使用 is_null() 函数判断方法和属性是否存在,但更加简洁。

<?php

// 自 PHP 8.0.0 起可用
$result = $repository?->getUser(5)?->name;

// 上边那行代码等价于以下代码
if (is_null($repository)) {
    $result = null;
} else {
    $user = $repository->getUser(5);
    if (is_null($user)) {
        $result = null;  // 注意此处
    } else {
        $result = $user->name;
    }
}

nullsafe 操作符和以上原来的属性、方法访问是一致的: 对象引用解析(dereference)为 null 时不抛出异常,而是返回 null

1.7 其他新特性#

  • 新增 WeakMap 类。

  • 新增 ValueError 类。

  • 现在,只要类型兼容,任意数量的函数参数都可以用一个可变参数替换。 例如允许编写下面的代码:

    <?php
    class A {
        public function method(int $many, string $parameters, $here) {}
    }
    class B extends A {
        public function method(...$everything) {}
    }
    
  • static ("后期静态绑定"中) 可以作为返回类型:

    <?php
    class Test {
        public function create(): static {
            return new static();
        }
    }
    
  • 现在可以通过 $object::class 获取类名,返回的结果和 get_class($object) 一致。

  • newinstanceof 可用于任何表达式, 用法为 new (expression)(...$args)$obj instanceof (expression)
  • 添加对一些变量语法一致性的修复,例如现在能够编写 Foo::BAR::$baz。
  • 添加 Stringable interface, 当一个类定义 __toString() 方法后会自动实现该接口。
  • Trait 可以定义私有抽象方法(abstract private method)。 类必须实现 trait 定义的该方法。
  • 可作为表达式使用 throw。 使得可以编写以下用法:

    <?php
    $fn = fn() => throw new Exception('Exception in arrow function');
    $user = $session->user ?? throw new Exception('Must have user');
    
  • 参数列表中的末尾逗号为可选。

    <?php
    function functionWithLongSignature(
        Type1 $parameter1,
        Type2 $parameter2, // <-- 这个逗号也被允许了
    ) {
    }
    
  • 现在允许 catch (Exception) 一个 exception 而无需捕获到变量中。

  • 支持 mixed 类型。
  • ……

2. 不向后兼容的变更#

2.1 字符串与数字的比较#

数字与非数字形式的字符串之间的非严格比较现在将首先将数字转为字符串,然后比较这两个字符串。 数字与数字形式的字符串之间的比较仍然像之前那样进行。

请注意,这意味着 0 == "not-a-number" 现在将被认为是 false

Comparison Before After
0 == "0" true true
0 == "0.0" true true
0 == "foo" true false
0 == "" true false
42 == " 42" true true
42 == "42foo" true false

2.2 其它不向后兼容的变更#

  • 与类名相同的方法名将不再被当做构造方法。应该使用 __construct() 来取代它。
  • create_function() 已被移除。可使用匿名函数代替。
  • each() 已被移除. 可使用 foreachArrayIterator 代替。
  • 移除了对象的 array_key_exists 作用,可使用 isset()property_exists() 代替。
  • 默认的 error_reporting 级别是 E_ALL
  • 一些资源 (resource) 已迁移到 object, 使用 is_resource() 会被替换为 false详情可查看
  • ……

3. 废弃的功能#

  • 如果带有默认值的参数后面跟着一个必要的参数,那么默认值就会无效。这在 PHP 8.0.0 中已被废弃,通常可以通过删除默认值,不影响现有功能:

    <?php
    function test($a = [], $b) {} // 之前
    function test($a, $b) {}      // 之后
    ?>
    

    这条规则的一个例外是 Type $param = null 形式的参数,其中 null 的默认值使得类型隐式为空。这种用法仍然是允许的,但仍建议使用显式可空类型。

    <?php
    function test(A $a = null, $b) {} // 旧写法,仍可用
    function test(?A $a, $b) {}       // 推荐写法
    ?>
    

原文


最后更新: 2022年4月6日 12:41:10