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 错误。

参考链接#

谢致#

感谢你的阅读,让我们一起探索知识,共同成长。

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。