Skip to content

PHP 8.1 新特性

PHP 8.1 带来了枚举(Enums)、只读属性(Readonly Properties)、纤程(Fibers)、never 返回类型、交叉类型(Intersection Types)和 first-class callable 语法等重量级特性。这些特性进一步完善了 PHP 的类型系统和并发处理能力。

前置知识

阅读本节前,建议先了解:PHP 8.0 新特性

枚举(Enums)

基本枚举

php
<?php
declare(strict_types=1);

// 基本枚举(无底类型)
enum Status
{
    case Active;
    case Inactive;
    case Pending;
}

// 使用
$userStatus = Status::Active;
echo $userStatus->name;  // "Active"

// match 配合枚举
function getStatusLabel(Status $status): string
{
    return match ($status) {
        Status::Active => '活跃',
        Status::Inactive => '未激活',
        Status::Pending => '待处理',
    };
}

Backed Enum(有底类型的枚举)

php
<?php
declare(strict_types=1);

// 字符串底类型
enum OrderStatus: string
{
    case Pending = 'pending';
    case Processing = 'processing';
    case Shipped = 'shipped';
    case Delivered = 'delivered';
    case Cancelled = 'cancelled';

    // 枚举方法
    public function canTransitionTo(self $newStatus): bool
    {
        $allowed = match ($this) {
            self::Pending => [self::Processing, self::Cancelled],
            self::Processing => [self::Shipped, self::Cancelled],
            self::Shipped => [self::Delivered],
            self::Delivered, self::Cancelled => [],
        };

        return in_array($newStatus, $allowed, true);
    }

    public function label(): string
    {
        return match ($this) {
            self::Pending => '待处理',
            self::Processing => '处理中',
            self::Shipped => '已发货',
            self::Delivered => '已送达',
            self::Cancelled => '已取消',
        };
    }

    public function isFinalState(): bool
    {
        return match ($this) {
            self::Delivered, self::Cancelled => true,
            default => false,
        };
    }
}

// 整数底类型
enum UserRole: int
{
    case Guest = 0;
    case Member = 1;
    case Editor = 2;
    case Admin = 3;

    public function hasPermission(string $permission): bool
    {
        $permissions = match ($this) {
            self::Guest => ['view'],
            self::Member => ['view', 'create', 'edit_own'],
            self::Editor => ['view', 'create', 'edit_own', 'edit_all'],
            self::Admin => ['*'],
        };

        return in_array('*', $permissions, true)
            || in_array($permission, $permissions, true);
    }
}

// 枚举值的使用
$status = OrderStatus::Processing;
echo $status->value;          // "processing"
echo $status->label();         // "处理中"

// 从值创建枚举
$status = OrderStatus::from('pending');     // OrderStatus::Pending
$status = OrderStatus::tryFrom('unknown');  // null(不存在时返回 null)

// 遍历所有枚举值
foreach (OrderStatus::cases() as $case) {
    echo "{$case->value}: {$case->label()}\n";
}

枚举接口与 Trait

php
<?php
declare(strict_types=1);

interface Colorful
{
    public function color(): string;
}

trait HasDescription
{
    public function description(): string
    {
        return "This is a " . $this->name . " entity";
    }
}

enum Priority: int implements Colorful
{
    use HasDescription;

    case Low = 1;
    case Medium = 2;
    case High = 3;

    public function color(): string
    {
        return match ($this) {
            self::Low => 'green',
            self::Medium => 'yellow',
            self::High => 'red',
        };
    }
}

echo Priority::High->color();        // "red"
echo Priority::Medium->description(); // "This is a Medium entity"

只读属性(Readonly Properties)

php
<?php
declare(strict_types=1);

class User
{
    public function __construct(
        public readonly string $name,
        public readonly string $email,
        public readonly int $age,
    ) {}

    // ❌ 错误:不能修改 readonly 属性
    // public function setName(string $name): void {
    //     $this->name = $name;  // Error: Cannot modify readonly property
    // }

    // ✅ 正确:readonly 属性只能在构造函数中初始化
    public static function fromArray(array $data): self
    {
        return new self(
            name: $data['name'],
            email: $data['email'],
            age: $data['age'],
        );
    }
}

// 只读属性可以用于普通属性(非构造器提升)
class Config
{
    public readonly string $appVersion;

    public function __construct()
    {
        $this->appVersion = '1.0.0';
    }
}

Readonly 限制

  • readonly 只能在类型声明的属性上使用(必须有类型)
  • readonly 只能在构造函数中或声明时赋值一次
  • static 属性不能是 readonly

Fibers(纤程)

基本概念

php
<?php
declare(strict_types=1);

// Fiber 是 PHP 8.1 引入的底层并发原语
// 它允许在函数调用栈中的任意位置暂停和恢复执行

$fiber = new Fiber(function (): void {
    $value = Fiber::suspend('first');
    echo "Received: {$value}\n";  // "Received: second"

    $value = Fiber::suspend('third');
    echo "Received: {$value}\n";  // "Received: fourth"
});

// 启动 Fiber
$result = $fiber->start();       // 返回 'first'
echo "Fiber returned: {$result}\n";  // "Fiber returned: first"

// 恢复 Fiber
$result = $fiber->resume('second');  // 打印 "Received: second",返回 'third'

$result = $fiber->resume('fourth'); // 打印 "Received: fourth"

实际应用:模拟异步 I/O

php
<?php
declare(strict_types=1);

class AsyncTask
{
    private Fiber $fiber;
    private mixed $result = null;
    private bool $completed = false;

    public function __construct(callable $callback)
    {
        $this->fiber = new Fiber(function () use ($callback): void {
            $this->result = $callback();
            $this->completed = true;
        });
    }

    public function run(): mixed
    {
        if ($this->completed) {
            return $this->result;
        }

        $this->fiber->start();

        return Fiber::suspend();
    }

    public function complete(): void
    {
        if (!$this->completed && $this->fiber->isStarted()) {
            $this->fiber->resume();
        }
    }

    public function isCompleted(): bool
    {
        return $this->completed;
    }
}

never 返回类型

php
<?php
declare(strict_types=1);

// never 表示函数永远不会正常返回
// 意味着函数要么抛出异常,要么调用 exit(),要么进入无限循环

function throwError(string $message): never
{
    throw new RuntimeException($message);
}

function redirect(string $url): never
{
    header("Location: {$url}");
    exit();
}

function handleCommand(string $command): never
{
    match ($command) {
        'quit' => exit(0),
        'help' => exit($this->showHelp()),
        default => throw new InvalidArgumentException("Unknown command: {$command}"),
    };
}

// 与 void 的区别
// void: 函数正常返回,但不返回值
// never: 函数永远不会返回

function logMessage(string $msg): void
{
    echo $msg . "\n";
    // 函数正常返回,没有返回值
}

function abort(string $msg): never
{
    throw new RuntimeException($msg);
    // 函数不会到达这里
}

交叉类型(Intersection Types)

php
<?php
declare(strict_types=1);

// 交叉类型:一个值必须同时满足多个类型约束
// 主要用于接口组合

function processEntity(Countable&Iterator $collection): int
{
    $count = count($collection);    // Countable 的方法
    $collection->rewind();         // Iterator 的方法
    return $count;
}

// 示例:同时实现多个接口
interface HasId
{
    public function getId(): string;
}

interface HasTimestamps
{
    public function getCreatedAt(): DateTimeImmutable;
    public function getUpdatedAt(): DateTimeImmutable;
}

// 函数参数要求同时实现两个接口
function saveEntity(HasId&HasTimestamps $entity): void
{
    $id = $entity->getId();
    $createdAt = $entity->getCreatedAt();
    // 保存到数据库...
}

First-class Callable 语法

php
<?php
declare(strict_types=1);

// PHP 8.0 及之前
$fn = strlen(...);
$fn = Closure::fromCallable('strlen');
$fn = [$this, 'method'](...);

// PHP 8.1+ First-class callable 语法
$fn = strlen(...);           // Closure
$fn = $array->push(...);     // 闭包
$fn = $obj->method(...);     // 闭包
$fn = User::create(...);     // 闭包

// 实际使用
class OrderService
{
    public function processOrders(array $orders, callable $processor): array
    {
        return array_map($processor, $orders);
    }
}

$service = new OrderService();

// 使用 first-class callable
$result = $service->processOrders($orders, $this->validateOrder(...));
$result = $service->processOrders($orders, fn ($o) => $this->validateOrder($o));  // PHP 7.4

其他改进

显式八进制数字格式

php
<?php
declare(strict_types=1);

// PHP 8.0 之前
$octal = 017;

// PHP 8.1+ 显式八进制
$octal = 0o17;  // 等同于 15(十进制)
$hex = 0x1F;    // 十六进制
$binary = 0b1111; // 二进制

array_is_list()

php
<?php
declare(strict_types=1);

// 检查数组是否是列表(从 0 开始的连续整数键)

array_is_list([]);                          // true
array_is_list([1, 2, 3]);                  // true
array_is_list(['a', 'b', 'c']);            // true
array_is_list([0 => 'a', 1 => 'b']);       // true
array_is_list([1 => 'a', 2 => 'b']);       // false(不从 0 开始)
array_is_list(['key' => 'value']);          // false(关联数组)
array_is_list([0 => 'a', 2 => 'b']);       // false(键不连续)

最佳实践

  1. 使用枚举替代常量类:枚举提供了类型安全和可枚举性
  2. 使用 readonly:对不变属性标记为 readonly,防止意外修改
  3. 使用 never:明确标记不会返回的函数
  4. 使用 first-class callable:更简洁的闭包创建方式

下一节

继续学习:PHP 8.2 新特性

参考链接