banner
RandyChan

RandyChan

深漂 / Back-end developer
github

PHP 8.1 列舉

枚舉(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。僅允許 intstring 作為枚舉值。

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 錯誤。

參考鏈接#

謝致#

感謝你的閱讀,讓我們一起探索知識,共同成長。

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。