Menu

PHP 枚举 enum 教程:from()、tryFrom() 和 cases()

PHP 枚举(8.1+)是只有固定几个取值的类型:enum Status: string { case Active = 'active'; }。内容包括纯枚举和 backed 枚举、->value 与 ->name、用 from() 和 tryFrom() 转换、用 cases() 列出所有 case、方法、常量、接口,以及在 match 中使用枚举。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

PHP 枚举(PHP 8.1+)定义了一个类型,它只有固定的几个可能取值,也就是它的 case:enum Status { case Active; case Banned; }。使用时写作 Status::Active,类型声明为 Status 的参数不接受其他任何值。backed 枚举(enum Status: string)还会给每个 case 一个值,你可以把它存起来,再用 Status::from() 转换回来。

在有枚举之前,同样的功能靠一组字符串常量实现,没有什么能阻止 'actve' 这样的拼写错误写进数据库。把 Status 用作参数类型后,只能传入这三个 case。

纯枚举和 backed 枚举

纯枚举的 case 只有名称。backed 枚举在名称后面声明一个类型,int 或 string,然后每个 case 都必须有一个该类型的唯一值。每个 case 都有 ->name;只有 backed case 才有 ->value。

只要值会离开程序(数据库列、URL、表单字段、JSON),就选 backed 枚举。只在代码内部存在的值,比如解析器的状态,选纯枚举。

用 from() 和 tryFrom() 转换值

backed 枚举有两个静态方法,可以把存储的值转换回 case。遇到未知值时,from() 抛出 ValueError;tryFrom() 返回 null,很适合配合 ?? 设置默认值:

对用户发来的任何数据用 tryFrom();对你自己的代码写入的值用 from(),这时出现未知值就说明有 bug,你会希望被告知。参数遵循普通的参数类型规则:对 int 枚举,在默认模式下 Priority::from('5') 返回 Priority::Normal,因为 '5' 会先被转换成 5;Priority::from('5x') 会产生 TypeError;而在 declare(strict_types=1) 下,任何字符串都会产生 TypeError。tryFrom() 也一样:Priority::tryFrom('abc') 会直接抛出异常,而不是返回 null,所以对 int 枚举,要先用 filter_var($raw, FILTER_VALIDATE_INT) 检查表单输入。

用 cases() 列出所有 case

Enum::cases() 按声明顺序返回包含所有 case 的数组。用它可以生成下拉菜单、校验列表,或者配合 array_column 获取所有值:

运行它,选一个套餐并点击 Choose:同一个脚本会带着填好的 $_POST['plan'] 再运行一次,tryFrom() 会把提交的字符串转换回 Plan 的 case。伪造的值(比如 plan=gold)会得到 null,也不会有选项被选中。

枚举的方法、常量和接口

枚举可以有方法、静态方法和常量,也可以实现接口。在方法内部,$this 就是当前的 case,所以 match ($this) 是给每个 case 附加数据的自然方式。枚举不能有属性:每个 case 的数据都通过方法提供。

在 match 中使用枚举

match 用 === 比较,而枚举的 case 只与自身全等,所以 match 和枚举是天然的搭配。如果你漏写了一个 case,而这个 case 又进入了 match,PHP 会抛出 UnhandledMatchError,而不是悄悄地什么都不返回:

match 与 switch 的区别见 match。

枚举与 JSON、字符串

json_encode() 把 backed case 编码为它的值。纯枚举无法编码,而对任何 case 使用 echo 都会失败,因为枚举是对象,不是字符串。应改为输出 ->value 或 ->name:

echo Role::Admin; 会抛出 Error("Object of class Role could not be converted to string"),而且枚举不能实现 __toString()。反过来读取时,JSON 给出的是普通字符串,所以要像上面那样用 from() 或 tryFrom() 转换。编码的更多内容见 json_encode。

使用枚举的常见错误

有三种错误反复出现:case 永远不等于它的值,case 不能作为数组键,new 不能用于枚举:

如果需要一个以 case 本身为键的映射,用 SplObjectStorage 或 WeakMap,它们接受对象作为键。

常见问题

PHP 中怎么获取枚举的值?

在 backed 枚举上读取 ->value:Status::Active->value 是 'active'。每个 case 还有 ->name,即字符串形式的 case 名('Active')。纯枚举(没有 : string 或 : int)只有 ->name。

PHP 枚举的 from() 和 tryFrom() 有什么区别?

两者都把一个值转换成 backed 枚举的 case。Status::from('active') 返回对应的 case,没有 case 对应这个值时抛出 ValueError;Status::tryFrom('nope') 则返回 null。处理用户输入时用 tryFrom(),值出错就意味着 bug 时用 from()。

PHP 中怎么获取枚举的所有值?

Status::cases() 按声明顺序返回所有 case。要获取 backed 枚举的所有值,用 array_column(Status::cases(), 'value');要获取所有名称,用 array_column(Status::cases(), 'name')。

纯枚举和 backed 枚举有什么区别?

纯枚举(enum Suit { case Hearts; })的 case 只有名称。backed 枚举(enum Suit: string { case Hearts = 'H'; })给每个 case 一个唯一的 int 或 string 值,要把它存进数据库、放进表单或 JSON,再用 from() 或 tryFrom() 转换回来,就需要这个值。

PHP 枚举可以有方法吗?

可以。枚举可以有方法、静态方法和常量,也可以实现接口。在方法内部,$this 就是当前的 case,所以 return match ($this) { self::Active => 'green', self::Banned => 'red' }; 是一种常见写法。枚举不能有属性。

Coddy 编程语言插图

和 Coddy 一起学编程

开始