PHP 8.1 枚举

枚举（Enum），能够让开发者自定义类型为一系列可能的离散值中的一个。 在定义领域模型中很有用，它能够 “隔离无效状态”（making invalid states unrepresentable）。

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

定义枚举

枚举类似 class，它和 class、interface、trait 共享同样的命名空间。 也能用同样的方式自动加载。

定义枚举使用 enum 关键字，使用 case 关键字声明枚举的值，例如定义一个帖子的状态枚举：

Copy
enum Status
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;
}

枚举的好处是它们代表了常量值的集合，但最重要的是，可以像这样传入这些值：

Copy
class Post
{
public function __construct(
public Status $status,
) {}
}

在此示例中，创建枚举并将其传递给 Post 看起来像这样：

Copy
$post = new Post(Status::DRAFT);

枚举方法

枚举可以定义方法，就像类一样。可以结合 match 操作。比如获取枚举值对应的颜色：

Copy
enum Status
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;

public function color(): string
{
return match($this)
{
Status::DRAFT => 'grey',
Status::PUBLISHED => 'green',
Status::ARCHIVED => 'red',
};
}
}

// 调用枚举方法
$status = Status::ARCHIVED;
$status->color(); // 'red'

同时也支持定义静态方法：

Copy
enum Status
{
// …

public static function getColor(Status $status): string
{
return $status->color();
}
}

枚举也支持使用 self 关键字代表当前枚举：

Copy
enum Status
{
// …

public function color(): string
{
return match($this)
{
self::DRAFT => 'grey',
self::PUBLISHED => 'green',
self::ARCHIVED => 'red',
};
}
}

枚举常量

枚举类可以包括 public、private、protected 的常量， 但由于它不支持继承，因此在实践中 private 和 protected 效果是相同的。

比如定义一个默认状态的常量：

Copy
enum Status
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;

public const DEFAULT = self::DRAFT;
}

枚举接口

枚举也可以像类一样实现接口

Copy
interface HasColor
{
public function color(): string;
}

Copy
enum Status implements HasColor
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;

public function color(): string { /* implement color */ }
}

枚举值（Backed enums）

你可以为每个枚举项分配一个值，枚举值由内部对象表示。

Copy
enum Status: string
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';
}

注意枚举定义中的类型声明。它表明所有枚举值都是给定类型的。你也可以使其成为 int 。仅允许 int 和 string 作为枚举值。

Copy
enum Status: int
{
case DRAFT = 1;
case PUBLISHED = 2;
case ARCHIVED = 3;
}

具有值的枚举叫做 “Backed enums”，Backed enums 能更好的序列化和写入数据库。如果决定分配枚举值，则所有枚举项都应具有值，不能混合并匹配它们。

Backed enums 也可以实现接口，implements 必须放在类型之后。

Copy
enum Status: string implements HasColor
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';

public function color(): string { /* implement color */ }
}

序列化枚举

枚举值支持序列化和反序列化。每个枚举值可以通过一个只读的 value 获取值。

Copy
$value = Status::DRAFT->value; // draft

也可以通过 Enum::from() 方法把值恢复为枚举项。from 方法接收枚举项的值，类型不匹配或者值不存在都会抛出 ValueError 异常。

Copy
$status = Status::from(1); // ValueError
$status = Status::from('unknown'); // ValueError
$status = Status::from('draft'); // Status::DRAFT

如果你不确定是否存在这个枚举值，并且期望不存在时返回 null 可以使用 Enum::tryFrom() 方法。

Copy
$status = Status::tryFrom('unknown'); // null
$status = Status::from('draft'); // Status::DRAFT

枚举列表

你可以使用 Enum::cases() 方法获取所有的枚举项。

Copy
Status::cases();

/* [
Status::DRAFT,
Status::PUBLISHED,
Status::ARCHIVED
] */

该方法返回的的类型为数组，包含每个枚举对象。你可以通过该方法循环遍历所有枚举。

Copy
array_map(
fn(Status $status) => $status->color(),
Status::cases()
); // ['grey', 'green', 'red']

枚举对象

实际上每个枚举值都是一个单例对象。这意味着你可以像这样进行比较枚举值：

Copy
$statusA = Status::PENDING;
$statusB = Status::PENDING;
$statusC = Status::ARCHIVED;

$statusA === $statusB; // true
$statusA === $statusC; // false
$statusC instanceof Status; // true

由于枚举值实际上是对象，因此目前无法将它们用作数组键。以下将导致错误。

Copy
$list = [
Status::DRAFT => 'draft', // TypeError
// …
];

Trait

枚举也能使用 trait，行为和 class 一样。 留意在枚举中 use trait 不允许包含属性。 只能包含方法、静态方法。 包含属性的 trait 会导致 fatal 错误。

参考链接

https://www.php.net/manual/zh/language.enumerations.overview.php

https://stitcher.io/blog/php-enums