枚舉(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 錯誤。
參考鏈接#
謝致#
感謝你的閱讀,讓我們一起探索知識,共同成長。