UnitEnum / BackedEnum 接口
概述
PHP 8.1 引入了枚举(Enum)功能,并提供了两个内置接口来支持枚举的反射和操作:
- UnitEnum — 所有枚举类型的公共父接口,提供
cases()静态方法获取所有枚举值 - BackedEnum — 带值枚举(
enum X: type)的接口,提供from()和tryFrom()方法进行值到枚举的转换
php
<?php
declare(strict_types=1);
// UnitEnum 接口
// interface UnitEnum
// {
// public static function cases(): array;
// }
// BackedEnum 接口
// interface BackedEnum extends UnitEnum
// {
// public static function from(int|string $value): static;
// public static function tryFrom(int|string $value): ?static;
// }版本说明
枚举功能在 PHP 8.1 中引入。所有枚举类型自动实现 UnitEnum。带值枚举(enum Status: string)额外实现 BackedEnum。不能被用户类直接实现这两个接口。
基础概念
枚举的基本形式
php
<?php
declare(strict_types=1);
// 纯枚举(Unit Enum)— 自动实现 UnitEnum
enum Status
{
case Active;
case Inactive;
case Pending;
}
// 带值枚举(Backed Enum)— 自动实现 BackedEnum(继承 UnitEnum)
enum UserRole: string
{
case Admin = 'admin';
case Editor = 'editor';
case Viewer = 'viewer';
}
enum Priority: int
{
case Low = 1;
case Medium = 2;
case High = 3;
case Critical = 4;
}枚举值的特性
- 枚举值是单例对象(每个 case 全局唯一)
- 枚举值可以拥有方法
- 枚举值可以有属性(PHP 8.2+)
- 枚举不能被实例化(除了 case 本身)
语法与代码
UnitEnum::cases()
php
<?php
declare(strict_types=1);
enum HttpMethod: string
{
case Get = 'GET';
case Post = 'POST';
case Put = 'PUT';
case Delete = 'DELETE';
case Patch = 'PATCH';
}
// cases() 返回所有枚举值的数组
$methods = HttpMethod::cases();
foreach ($methods as $method) {
echo "{$method->name} = {$method->value}\n";
// Get = GET
// Post = POST
// ...
}
// cases() 返回类型:array<int, static>
// 键是枚举定义顺序的数字索引
echo $methods[0]->name; // Get
// 获取枚举值名称列表
$names = array_map(fn (HttpMethod $m): string => $m->name, HttpMethod::cases());
// ['Get', 'Post', 'Put', 'Delete', 'Patch']
// 获取枚举值的值列表(仅 BackedEnum)
$values = array_map(fn (HttpMethod $m): string => $m->value, HttpMethod::cases());
// ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']BackedEnum::from() 和 tryFrom()
php
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Created = 'created';
case Paid = 'paid';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
}
// from() — 值转枚举(不存在则抛出 ValueError)
$status = OrderStatus::from('paid');
echo $status->name; // Paid
echo $status->value; // paid
// tryFrom() — 值转枚举(不存在返回 null)
$status = OrderStatus::tryFrom('expired');
var_dump($status); // null
// tryFrom 的典型用法
$inputStatus = $_GET['status'] ?? '';
$orderStatus = OrderStatus::tryFrom($inputStatus);
if ($orderStatus === null) {
throw new InvalidArgumentException("无效的状态值: {$inputStatus}");
}
// from() 在确认值有效时使用
function updateOrder(int $orderId, string $statusValue): void
{
// 数据库中存储的是合法枚举值,可以安全使用 from()
$status = OrderStatus::from($statusValue);
echo "订单 {$orderId} 状态更新为 {$status->name}";
}详细说明
枚举方法
php
<?php
declare(strict_types=1);
enum Color: string
{
case Red = '#FF0000';
case Green = '#00FF00';
case Blue = '#0000FF';
// 枚举可以定义方法
public function rgb(): array
{
return [
hexdec(substr($this->value, 1, 2)),
hexdec(substr($this->value, 3, 2)),
hexdec(substr($this->value, 5, 2)),
];
}
public function isWarm(): bool
{
return in_array($this, [self::Red, self::Green]);
}
// 静态方法作为工厂
public static function fromRgb(int $r, int $g, int $b): self
{
$hex = sprintf('#%02X%02X%02X', $r, $g, $b);
return self::from($hex);
}
}
$red = Color::Red;
echo $red->rgb()[0]; // 255
echo $red->isWarm() ? '暖色' : '冷色'; // 暖色枚举实现接口
php
<?php
declare(strict_types=1);
interface Describable
{
public function description(): string;
}
enum LogLevel: int implements Describable
{
case Debug = 0;
case Info = 1;
case Warning = 2;
case Error = 3;
case Critical = 4;
public function description(): string
{
return match ($this) {
self::Debug => '调试信息',
self::Info => '一般信息',
self::Warning => '警告信息',
self::Error => '错误信息',
self::Critical => '严重错误',
};
}
public function isSevere(): bool
{
return $this->value >= self::Error->value;
}
}
$level = LogLevel::from(3);
echo $level->description(); // 错误信息
echo $level->isSevere() ? '严重' : '一般'; // 严重traits 在枚举中的使用
php
<?php
declare(strict_types=1);
trait HasLabel
{
abstract public function label(): string;
}
enum PaymentMethod: string
{
use HasLabel;
case CreditCard = 'credit_card';
case Alipay = 'alipay';
case WechatPay = 'wechat_pay';
case BankTransfer = 'bank_transfer';
public function label(): string
{
return match ($this) {
self::CreditCard => '信用卡',
self::Alipay => '支付宝',
self::WechatPay => '微信支付',
self::BankTransfer => '银行转账',
};
}
public function feeRate(): float
{
return match ($this) {
self::CreditCard => 0.006,
self::Alipay => 0.001,
self::WechatPay => 0.001,
self::BankTransfer => 0.005,
};
}
}
$method = PaymentMethod::tryFrom('alipay');
echo $method?->label(); // 支付宝
echo $method?->feeRate(); // 0.001枚举属性(PHP 8.2+)
php
<?php
declare(strict_types=1);
// PHP 8.2+ 支持枚举常量属性
enum FeatureFlag
{
case DarkMode;
case NewDashboard;
case BetaAPI;
// PHP 8.2+ 枚举属性
#[\Attribute]
public const IS_DEFAULT = [
DarkMode::class => true,
];
}
// PHP 8.2+ 的枚举属性(用常量表达元数据)
enum ApiVersion: string
{
case V1 = 'v1';
case V2 = 'v2';
case V3 = 'v3';
public const DEPRECATED = [
self::V1,
];
public function isDeprecated(): bool
{
return in_array($this, self::DEPRECATED, true);
}
}实战示例
枚举在状态机中的应用
php
<?php
declare(strict_types=1);
enum OrderState: string
{
case Draft = 'draft';
case Confirmed = 'confirmed';
case Paid = 'paid';
case Processing = 'processing';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
/**
* 获取允许的下一状态
* @return array<int, self>
*/
public function allowedTransitions(): array
{
return match ($this) {
self::Draft => [self::Confirmed, self::Cancelled],
self::Confirmed => [self::Paid, self::Cancelled],
self::Paid => [self::Processing, self::Cancelled],
self::Processing => [self::Shipped],
self::Shipped => [self::Delivered],
self::Delivered => [],
self::Cancelled => [],
};
}
/**
* 检查是否可以转换到指定状态
*/
public function canTransitionTo(self $target): bool
{
return in_array($target, $this->allowedTransitions(), true);
}
/**
* 执行状态转换
*/
public function transitionTo(self $target): self
{
if (!$this->canTransitionTo($target)) {
throw new InvalidArgumentException(
"不能从 {$this->value} 转换到 {$target->value}"
);
}
return $target;
}
public function isFinal(): bool
{
return empty($this->allowedTransitions());
}
}
// 使用
$currentState = OrderState::Draft;
$newState = $currentState->transitionTo(OrderState::Confirmed);
echo $newState->value; // confirmed
try {
OrderState::Draft->transitionTo(OrderState::Shipped);
} catch (InvalidArgumentException $e) {
echo $e->getMessage(); // 不能从 draft 转换到 shipped
}枚举在数据库映射中的应用
php
<?php
declare(strict_types=1);
enum DbEnum: string
{
case Active = 'active';
case Inactive = 'inactive';
case Archived = 'archived';
}
// 用于 Eloquent/Doctrine 等 ORM 的枚举转换 trait
trait BackedEnumCast
{
/**
* 从数据库值创建枚举
*/
public static function fromDatabase(string|int $value): static
{
return static::from($value);
}
/**
* 转换为数据库存储值
*/
public function toDatabase(): string|int
{
return $this->value;
}
}
// 使用
enum UserStatus: string
{
use BackedEnumCast;
case Active = 'active';
case Inactive = 'inactive';
case Banned = 'banned';
}
// 从数据库读取
$rawStatus = 'active';
$status = UserStatus::fromDatabase($rawStatus);
echo $status->name; // Active
// 写入数据库
$dbValue = $status->toDatabase();
echo $dbValue; // active注意事项
枚举值的比较
php
<?php
declare(strict_types=1);
enum Status: string
{
case Active = 'active';
case Inactive = 'inactive';
}
$a = Status::Active;
$b = Status::Active;
$c = Status::Inactive;
// 枚举值是单例,可以用 === 比较
var_dump($a === $b); // bool(true)
var_dump($a === $c); // bool(false)
var_dump($a == $b); // bool(true)
// 比较值(而不是身份)
var_dump($a->value === 'active'); // bool(true)
// match 使用枚举值(推荐用 match 而非 switch)
$result = match ($a) {
Status::Active => '活跃',
Status::Inactive => '非活跃',
};枚举的限制
php
<?php
declare(strict_types=1);
// 枚举不能:
// 1. 被手动实例化(new Status() 会报错)
// 2. 拥有构造函数参数
// 3. 继承其他枚举或类
// 4. 被继承
// 5. 有动态属性
// 6. 实现 clone/unserialize
// 以下都是非法的:
// new Status(); // 错误
// enum ChildStatus extends Status // 错误
// 但枚举可以:
// - 定义方法
// - 实现接口
// - 使用 traits
// - 作为类型提示
// - 作为数组键(通过 SplFixedArray 等方式)枚举限制
枚举是单例模式,不能被 new 实例化。枚举不能被继承(final class 是隐含的)。枚举不能有构造函数参数。枚举 case 不能有对象属性。
最佳实践
- 始终使用 tryFrom 处理外部输入:来自用户输入或外部系统的值使用
tryFrom(),避免异常 - 使用 match 替代 switch:
match对枚举值更安全,支持表达式和穷尽检查 - 为枚举添加有意义的方法:将枚举相关逻辑封装在枚举类中,而不是散落在其他类中
- 使用枚举类型声明:函数参数和返回值使用枚举类型,获得编译时类型安全
- 带值枚举的值选择:
int更紧凑,string更可读(如 API 接口)
php
<?php
declare(strict_types=1);
// 最佳实践:完整枚举模板
enum ErrorCode: int implements Stringable
{
use HasLabel;
case NotFound = 404;
case ServerError = 500;
case Unauthorized = 401;
case Forbidden = 403;
public function label(): string
{
return match ($this) {
self::NotFound => '资源未找到',
self::ServerError => '服务器错误',
self::Unauthorized => '未授权',
self::Forbidden => '禁止访问',
};
}
public function httpStatus(): int
{
return $this->value;
}
public function __toString(): string
{
return "Error({$this->value}): {$this->label()}";
}
}
// 类型安全的函数
function handleError(ErrorCode $code, string $context): string
{
return "{$code} — {$context}";
}
echo handleError(ErrorCode::NotFound, '用户不存在');
// Error(404): 资源未找到 — 用户不存在