Skip to content

带值枚举 Backed Enum

概述

带值枚举(Backed Enum)是 PHP 8.1 引入的枚举变体,每个 case 关联一个标量值(intstring)。这使得枚举可以与数据库、API 等外部系统进行数据交换。Backed Enum 自动实现 BackedEnum 接口,提供 from()tryFrom() 方法用于值到枚举的转换。

版本要求

Backed Enum 是 PHP 8.1+ 专属特性。

基础概念

Backed Enum 的类型

支持的标量类型语法示例
intenum Name: intcase Active = 1;
stringenum Name: stringcase Active = 'active';

注意

不支持 floatboolnull 作为枚举的标量类型。intstring 是唯一合法的 Backed Enum 类型。

BackedEnum 接口

Backed Enum 自动实现 BackedEnum 接口,该接口继承自 UnitEnum,提供:

  • from(int|string $value): static — 从值创建枚举,值无效时抛出异常
  • tryFrom(int|string $value): ?static — 从值创建枚举,值无效时返回 null
  • value 属性 — 只读,返回枚举关联的标量值

语法与代码

字符串型 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 的标量类型只能是 intstring
  • 每个 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;
    }
}

参考链接