Skip to content

PHP 8.1

PHP 8.1 发布于 2021 年 11 月 25 日, 安全支持截止日期为 2025 年 12 月 31 日,当前是 PHP 语言的一个主版本更新。

它包含了许多新功能,包括枚举、只读属性、First-class 可调用语法、纤程、交集类型和性能改进等。

枚举

在 PHP 中, 枚举是一种特殊类型的对象。Enum 本身是一个类(Class), 它的各种条目(case)是这个类的单例对象,意味着也是个有效对象 —— 包括类型的检测,能用对象的地方,也可以用它。

最常见的枚举例子是内置的布尔类型, 该枚举类型有两个有效值 truefalse。 Enum 使开发者能够任意定义出用户自己的、足够健壮的枚举。

基本用法

php
<?php
enum SortOrder
{
    case ASC;
    case DESC;
}

function query($fields, $filter, SortOrder $order = SortOrder::ASC) {
//
}
?>

由于确保 $order 不是 SortOrder::ASC 就是 SortOrder::DESC,所以 query() 函数能安全处理。 因为其他任意值都会导致 TypeError, 所以不需要额外的错误检查。

高级用法

php
<?php
enum UserStatus: string
{
    case Pending = 'P';
    case Active = 'A';
    case Suspended = 'S';
    case CanceledByUser = 'C';

    public function label(): string
    {
        return static::getLabel($this);
    }

    public static function getLabel(self $value): string
    {
        return match($value) {
            self::Pending => 'Pending',
            self::Active => 'Active',
            self::Suspended => 'Suspended',
            self::CanceledByUser => 'Canceled by user',
        };
    }
}

UserStatus::Pending->name;                  // 获取枚举名
UserStatus::Pending->value;                 // 获取枚举值, 比如例子中的字符串
UserStatus::cases();                        // 获取枚举列表
UserStatus::Pending->label();               // 调用枚举方法获取对应返回值
UserStatus::getLabel(UserStatus::Pending);  // 枚举包含静态方法

// 渲染下拉选项结构
foreach (UserStatus::cases() as $case) {
    printf('<option value="%s">%s</option>\n', $case->value, $case->label());
}

// UserStatus::Pending === 'P'; // 错误的写法,不应该用枚举值跟标量值进行比对
// UserStatus::Pending === UserStatus::from('P'); // 可以通过 from 静态方法获取枚举实例
// UserStatus::Pending === UserStatus::tryFrom('P'); // 或者通过 tryFrom 静态方法获取枚举实例,当值不存在时会返回NULL

用户的状态是 UserStatus::PendingUserStatus::ActiveUserStatus::SuspendedUserStatus::CanceledByUser 中的一个,具有独占性。 函数可以根据 UserStatus 设置参数类型,仅支持这四种值。

所有四个值都有一个 label() 方法,返回了人类可读的字符串。

它独立于等同于标量的“机器名”。 机器名用于类似数据库字段或 HTML 选择框这样的地方。

Transformer 或 Generator 方法

可以对枚举的值或名称的添加公共方法对其进行转换,也可以使用它来生成另一个值。

php
enum Category: string
{
    case Tech = "technologies";
    case Life = "life-stories";
    case Fun = "fun-activities";

    public function link(): string
    {
        return "https://mysite.com/articles/" . $this->value;
    }

    public function heading(): string
    {
        return strtoupper($this->name);
    }

    public function imageGenerator(): ImageGenerator
    {
        $generator = $this->name . "ImageGenerator";

        return new $generator;
    }
}

判断值相等

使用 === 操作符来判断枚举对象是否相等。

php
<?php

enum HttpStatusCode: int
{
    case Ok = 200;
    case Created = 201;
}

var_dump(HttpStatusCode::Ok === HttpStatusCode::Created); // false
var_dump(HttpStatusCode::Ok === HttpStatusCode::Ok); // true

字符串键数组解包

PHP 7.4之后版本中已经添加通过扩展运算符对数组内部进行解包支持,但前提是数组具有整数键。

php
<?php

# PHP > 7.4
[...[1,2,3], ...[4,5,6]];

现在也可以使用字符串键解包数组。如下:

php
<?php

# PHP < 8.1
$attributes = ['title' => 'My Blog', 'body' => 'My blog body'];
$additional = ['category_id' => 1];

array_merge($attributes, $additional); // ['title' => 'My Blog', 'body' => 'My blog body', 'category_id' => 1]
php
<?php

# PHP 8.1
$attributes = ['title' => 'My Blog', 'body' => 'My blog body'];
$additional = ['category_id' => 1];

[...$attributes, ...$additional];

返回类型 never

使用 never 类型声明的函数或方法表示它不会返回值。

php
<?php

# PHP 8.1
function redirect(string $uri): never
{
    header('Location: ' . $uri);
    return 'some code'; // Fatal error: A never-returning function must not return 这里不应该有 return 语句
}

如果在之前版本上面的代码不会抛出异常。

构造函数初始化器

php
<?php
// 7.4 <= PHP < 8.1

interface Logger { }

class NullLogger implements Logger { }

class Service
{
    private Logger $logger;

    public function __construct(?Logger $logger = null) {
        $this->logger = $logger ?? new NullLogger();
    }
}

对象现在可以用作默认参数值、静态变量和全局常量以及属性参数。

php
<?php

// PHP > 8.1
interface Logger { }

class NullLogger implements Logger { }

class Service
{
    public function __construct(private Logger $logger = new NullLogger) { }
}

类的只读属性

只读属性不能在初始化后再对其进行更改。

php
<?php

// 7.4 <= PHP < 8.1
class Project {
    protected string $uuid;
    
    public function __construct(string $uuid)
    {
        $this->uuid = $uuid;
    }

    public function getUuid()
    {
        return $this->uuid;
    }
}

$project = new Project('xx');

print_r($project->getUuid());

在之前的 PHP 版本中,如果要保护类属性不被外部重新赋值,需要将类属性设置为 protected,然后在类中定义一个公开方法返回这个私有变量。

在 PHP 8.1 之后的版本中添加了一个关键字 readonly 来修饰变量,只读:

php
<?php

// PHP > 8.1
class Project {
    public function __construct(public readonly string $uuid)
    {
    }
}

$project = new Project('xx');

print_r($project->uuid);
$project->uuid = 'xxx'; // Cannot modify readonly property Project::$uuid

MIT Licensed