带值枚举 Backed Enum
概述
带值枚举(Backed Enum)是 PHP 8.1 引入的枚举变体,每个 case 关联一个标量值(int 或 string)。这使得枚举可以与数据库、API 等外部系统进行数据交换。Backed Enum 自动实现 BackedEnum 接口,提供 from() 和 tryFrom() 方法用于值到枚举的转换。
版本要求
Backed Enum 是 PHP 8.1+ 专属特性。
基础概念
Backed Enum 的类型
| 支持的标量类型 | 语法 | 示例 |
|---|---|---|
int | enum Name: int | case Active = 1; |
string | enum Name: string | case Active = 'active'; |
注意
不支持 float、bool 或 null 作为枚举的标量类型。int 和 string 是唯一合法的 Backed Enum 类型。
BackedEnum 接口
Backed Enum 自动实现 BackedEnum 接口,该接口继承自 UnitEnum,提供:
from(int|string $value): static— 从值创建枚举,值无效时抛出异常tryFrom(int|string $value): ?static— 从值创建枚举,值无效时返回nullvalue属性 — 只读,返回枚举关联的标量值
语法与代码
字符串型 Backed Enum
php
<?php
declare(strict_types=1);
enum Status: string
{
case Pending = 'pending';
case Approved = 'approved';
case Rejected = 'rejected';
}
// 获取值
echo Status::Pending->value; // "pending"
// 从值创建枚举
$status = Status::from('approved'); // Status::Approved
echo $status->name; // "Approved"
// 安全创建(不抛出异常)
$status = Status::tryFrom('invalid'); // null
$status = Status::tryFrom('pending'); // Status::Pending整数型 Backed Enum
php
<?php
declare(strict_types=1);
enum Priority: int
{
case Low = 1;
case Medium = 2;
case High = 3;
case Critical = 4;
}
$priority = Priority::High;
echo $priority->value; // 3
// 从整数创建
$p = Priority::from(2); // Priority::Medium
$p = Priority::tryFrom(99); // null与 match 表达式结合
php
<?php
declare(strict_types=1);
enum UserRole: string
{
case Admin = 'admin';
case Editor = 'editor';
case Viewer = 'viewer';
public function label(): string
{
return match ($this) {
self::Admin => '管理员',
self::Editor => '编辑者',
self::Viewer => '访客',
};
}
public function permissions(): array
{
return match ($this->value) {
'admin' => ['read', 'write', 'delete', 'manage'],
'editor' => ['read', 'write'],
'viewer' => ['read'],
};
}
}
// 通过 match 分发
function handleRole(UserRole $role): string
{
return match ($role) {
UserRole::Admin => '管理面板',
UserRole::Editor => '编辑面板',
UserRole::Viewer => '查看面板',
};
}from() 与 tryFrom() 的区别
php
<?php
declare(strict_types=1);
enum Color: string
{
case Red = '#FF0000';
case Green = '#00FF00';
case Blue = '#0000FF';
}
// from() - 值无效时抛出 ValueError
try {
$color = Color::from('#FFFFFF'); // ValueError
} catch (\ValueError $e) {
echo "Invalid color: " . $e->getMessage();
}
// tryFrom() - 值无效时返回 null
$color = Color::tryFrom('#FFFFFF'); // null
if ($color === null) {
echo "Color not found";
}值必须是唯一的
php
<?php
declare(strict_types=1);
enum ErrorCode: int
{
case NotFound = 404;
case ServerError = 500;
// case Redirect = 404; // Fatal error: Duplicate value in enum
}详细说明
Backed Enum 与数据库交互
php
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Created = 'created';
case Paid = 'paid';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
}
class OrderRepository
{
public function find(int $id): ?array
{
// 从数据库获取原始字符串值
return ['id' => $id, 'status' => 'paid'];
}
public function getStatus(int $id): ?OrderStatus
{
$row = $this->find($id);
if ($row === null) {
return null;
}
return OrderStatus::tryFrom($row['status']);
}
public function updateStatus(int $id, OrderStatus $status): void
{
// 存储枚举的标量值到数据库
$value = $status->value; // 'paid', 'shipped' 等
// SQL: UPDATE orders SET status = :status WHERE id = :id
}
}Backed Enum 与 JSON 交互
php
<?php
declare(strict_types=1);
enum PaymentMethod: string
{
case CreditCard = 'credit_card';
case Alipay = 'alipay';
case Wechat = 'wechat';
case BankTransfer = 'bank_transfer';
}
class PaymentDto
{
public function __construct(
public readonly int $amount,
public readonly PaymentMethod $method
) {}
public static function fromArray(array $data): self
{
return new self(
$data['amount'],
PaymentMethod::tryFrom($data['method'])
?? throw new \InvalidArgumentException("Invalid payment method")
);
}
public function toArray(): array
{
return [
'amount' => $this->amount,
'method' => $this->method->value,
];
}
}
// JSON 解码
$json = '{"amount": 100, "method": "alipay"}';
$dto = PaymentDto::fromArray(json_decode($json, true));
echo $dto->method->name; // "Alipay"
// JSON 编码
echo json_encode($dto->toArray()); // {"amount":100,"method":"alipay"}from 与 tryFrom 的选择
| 方法 | 值无效时行为 | 适用场景 |
|---|---|---|
from() | 抛出 ValueError | 值必须是合法的,非法是 bug |
tryFrom() | 返回 null | 值可能来自外部输入(用户、API) |
实战示例
场景一:API 状态码枚举
php
<?php
declare(strict_types=1);
enum ApiErrorCode: int
{
case Success = 0;
case InvalidParameter = 1001;
case Unauthorized = 1002;
case Forbidden = 1003;
case NotFound = 1004;
case InternalError = 5000;
public function message(): string
{
return match ($this) {
self::Success => '操作成功',
self::InvalidParameter => '参数错误',
self::Unauthorized => '未授权',
self::Forbidden => '禁止访问',
self::NotFound => '资源不存在',
self::InternalError => '服务器内部错误',
};
}
public function httpStatus(): int
{
return match ($this) {
self::Success => 200,
self::InvalidParameter => 422,
self::Unauthorized => 401,
self::Forbidden => 403,
self::NotFound => 404,
self::InternalError => 500,
};
}
public static function fromHttpStatus(int $code): ?self
{
return match ($code) {
200 => self::Success,
422 => self::InvalidParameter,
401 => self::Unauthorized,
403 => self::Forbidden,
404 => self::NotFound,
500 => self::InternalError,
default => null,
};
}
}场景二:数据库字段映射
php
<?php
declare(strict_types=1);
enum Gender: string
{
case Male = 'M';
case Female = 'F';
case Other = 'O';
public function label(): string
{
return match ($this) {
self::Male => '男',
self::Female => '女',
self::Other => '其他',
};
}
}
class UserMapper
{
public function toDomain(array $row): array
{
return [
'name' => $row['name'],
'gender' => Gender::tryFrom($row['gender']),
];
}
public function toDatabase(array $data): array
{
return [
'name' => $data['name'],
'gender' => $data['gender']?->value,
];
}
}注意事项
注意事项
- Backed Enum 的标量类型只能是
int或string - 每个 case 的值必须唯一,不能重复
from()在值无效时抛出ValueError,需要 try/catch 处理- Backed Enum 的
value属性是只读的 - 枚举的类型一旦声明就不能混合使用 int 和 string
小贴士
- 当值来自用户输入或外部系统时,使用
tryFrom()避免异常 - 当值来自内部可信来源时,使用
from()快速失败 - 将 Backed Enum 用于数据库字段映射,提供类型安全的数据交换
最佳实践
1. 选择合适的标量类型
php
<?php
declare(strict_types=1);
// 数据库存储用 string(可读性好)
enum DbStatus: string
{
case Active = 'active';
case Inactive = 'inactive';
}
// API/内部逻辑用 int(性能好、节省空间)
enum StatusCode: int
{
case Ok = 0;
case Error = 1;
}2. 始终提供安全的 tryFrom 方式
php
<?php
declare(strict_types=1);
enum Color: string
{
case Red = 'red';
case Blue = 'blue';
public static function safeFrom(string $value, self $default = self::Red): self
{
return self::tryFrom($value) ?? $default;
}
}3. 使用 value 属性而非 name 进行存储
php
<?php
declare(strict_types=1);
enum Priority: int
{
case Low = 1;
case Medium = 2;
case High = 3;
// 存储 value 而非 name
public function toDatabase(): int
{
return $this->value;
}
}