枚举(Enum),能够让开发者自定义类型为一系列可能的离散值中的一个。 在定义领域模型中很有用,它能够 “隔离无效状态”(making invalid states unrepresentable)。
在 PHP 中, 枚举是一种特殊类型的对象。Enum 本身是一个类(Class), 它的各种条目(case)是这个类的单例对象,意味着也是个有效对象 —— 包括类型的检测,能用对象的地方,也可以用它。
定义枚举#
枚举类似 class,它和 class、interface、trait 共享同样的命名空间。 也能用同样的方式自动加载。
定义枚举使用 enum 关键字,使用 case 关键字声明枚举的值,例如定义一个帖子的状态枚举:
enum Status
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;
}
枚举的好处是它们代表了常量值的集合,但最重要的是,可以像这样传入这些值:
class Post
{
public function __construct(
public Status $status,
) {}
}
在此示例中,创建枚举并将其传递给 Post 看起来像这样:
$post = new Post(Status::DRAFT);
枚举方法#
枚举可以定义方法,就像类一样。可以结合 match
操作。比如获取枚举值对应的颜色:
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'
同时也支持定义静态方法:
enum Status
{
// …
public static function getColor(Status $status): string
{
return $status->color();
}
}
枚举也支持使用 self
关键字代表当前枚举:
enum Status
{
// …
public function color(): string
{
return match($this)
{
self::DRAFT => 'grey',
self::PUBLISHED => 'green',
self::ARCHIVED => 'red',
};
}
}
枚举常量#
枚举类可以包括 public、private、protected 的常量, 但由于它不支持继承,因此在实践中 private 和 protected 效果是相同的。
比如定义一个默认状态的常量:
enum Status
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;
public const DEFAULT = self::DRAFT;
}
枚举接口#
枚举也可以像类一样实现接口
interface HasColor
{
public function color(): string;
}
enum Status implements HasColor
{
case DRAFT;
case PUBLISHED;
case ARCHIVED;
public function color(): string { /* implement color */ }
}
枚举值(Backed enums)#
你可以为每个枚举项分配一个值,枚举值由内部对象表示。
enum Status: string
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';
}
注意枚举定义中的类型声明。它表明所有枚举值都是给定类型的。你也可以使其成为 int
。仅允许 int
和 string
作为枚举值。
enum Status: int
{
case DRAFT = 1;
case PUBLISHED = 2;
case ARCHIVED = 3;
}
具有值的枚举叫做 “Backed enums”,Backed enums 能更好的序列化和写入数据库。如果决定分配枚举值,则所有枚举项都应具有值,不能混合并匹配它们。
Backed enums 也可以实现接口,implements
必须放在类型之后。
enum Status: string implements HasColor
{
case DRAFT = 'draft';
case PUBLISHED = 'published';
case ARCHIVED = 'archived';
public function color(): string { /* implement color */ }
}
序列化枚举#
枚举值支持序列化和反序列化。每个枚举值可以通过一个只读的 value
获取值。
$value = Status::DRAFT->value; // draft
也可以通过 Enum::from()
方法把值恢复为枚举项。from 方法接收枚举项的值,类型不匹配或者值不存在都会抛出 ValueError
异常。
$status = Status::from(1); // ValueError
$status = Status::from('unknown'); // ValueError
$status = Status::from('draft'); // Status::DRAFT
如果你不确定是否存在这个枚举值,并且期望不存在时返回 null
可以使用 Enum::tryFrom()
方法。
$status = Status::tryFrom('unknown'); // null
$status = Status::from('draft'); // Status::DRAFT
枚举列表#
你可以使用 Enum::cases()
方法获取所有的枚举项。
Status::cases();
/* [
Status::DRAFT,
Status::PUBLISHED,
Status::ARCHIVED
] */
该方法返回的的类型为数组,包含每个枚举对象。你可以通过该方法循环遍历所有枚举。
array_map(
fn(Status $status) => $status->color(),
Status::cases()
); // ['grey', 'green', 'red']
枚举对象#
实际上每个枚举值都是一个单例对象。这意味着你可以像这样进行比较枚举值:
$statusA = Status::PENDING;
$statusB = Status::PENDING;
$statusC = Status::ARCHIVED;
$statusA === $statusB; // true
$statusA === $statusC; // false
$statusC instanceof Status; // true
由于枚举值实际上是对象,因此目前无法将它们用作数组键。以下将导致错误。
$list = [
Status::DRAFT => 'draft', // TypeError
// …
];
Trait#
枚举也能使用 trait,行为和 class 一样。 留意在枚举中 use trait 不允许包含属性。 只能包含方法、静态方法。 包含属性的 trait 会导致 fatal 错误。
参考链接#
谢致#
感谢你的阅读,让我们一起探索知识,共同成长。