2025年,PHP 8.4 的枚举(Enum)和模式匹配(Pattern Matching)已经成为构建类型安全、可维护业务逻辑的核心工具。本文通过四个完整案例,带你掌握枚举与模式匹配的实战用法。
1. 为什么需要枚举和模式匹配?
传统 PHP 中,我们常用常量或字符串表示有限状态,但这种方式缺乏类型安全。枚举提供了强类型的状态集合,而模式匹配则让条件分支更加清晰、可读。它们配合使用,可以大幅减少 if-else 和 switch 的冗余代码。
- 枚举:类型安全、不可变、可附加方法
- 模式匹配:结构化条件判断、穷尽性检查
2. 枚举基础:定义与使用
PHP 8.1 引入枚举,8.4 增强了模式匹配能力。枚举可以包含方法、实现接口,并且支持纯枚举(Pure Enum)和回退枚举(Backed Enum)。
// 纯枚举(Pure Enum)
enum OrderStatus
{
case Pending;
case Paid;
case Shipped;
case Delivered;
case Cancelled;
}
// 回退枚举(Backed Enum):关联整型或字符串
enum PaymentMethod: string
{
case CreditCard = 'credit_card';
case PayPal = 'paypal';
case BankTransfer = 'bank_transfer';
// 枚举可以包含方法
public function label(): string
{
return match($this) {
self::CreditCard => '信用卡',
self::PayPal => 'PayPal',
self::BankTransfer => '银行转账',
};
}
}
// 使用枚举
$status = OrderStatus::Paid;
echo $status->name; // 输出: Paid
echo $status->value; // 纯枚举没有value,回退枚举有
$payment = PaymentMethod::PayPal;
echo $payment->value; // 输出: paypal
echo $payment->label(); // 输出: PayPal
3. 模式匹配基础:match 表达式增强
PHP 8.4 的 match 表达式支持更丰富的模式匹配,包括枚举匹配、类型匹配、条件匹配等。
// 基础 match 匹配枚举
function getStatusText(OrderStatus $status): string
{
return match($status) {
OrderStatus::Pending => '等待支付',
OrderStatus::Paid => '已支付',
OrderStatus::Shipped => '已发货',
OrderStatus::Delivered => '已送达',
OrderStatus::Cancelled => '已取消',
};
}
// 带条件的 match
function getDiscount(float $total): float
{
return match(true) {
$total > 1000 => $total * 0.2,
$total > 500 => $total * 0.15,
$total > 100 => $total * 0.1,
default => 0,
};
}
// 类型匹配(PHP 8.4 增强)
function processValue(mixed $value): string
{
return match(true) {
$value instanceof OrderStatus => '状态: ' . $value->name,
is_string($value) => '字符串: ' . $value,
is_int($value) && $value > 0 => '正整数: ' . $value,
default => '其他类型',
};
}
4. 实战案例一:订单状态机
使用枚举和模式匹配实现一个完整的订单状态机,确保状态转换的合法性。
enum OrderStatus
{
case Pending;
case Paid;
case Shipped;
case Delivered;
case Cancelled;
// 定义允许的状态转换
public function canTransitionTo(self $newStatus): bool
{
return match($this) {
self::Pending => in_array($newStatus, [self::Paid, self::Cancelled], true),
self::Paid => in_array($newStatus, [self::Shipped, self::Cancelled], true),
self::Shipped => in_array($newStatus, [self::Delivered], true),
self::Delivered => false, // 最终状态
self::Cancelled => false, // 最终状态
};
}
public function label(): string
{
return match($this) {
self::Pending => '待支付',
self::Paid => '已支付',
self::Shipped => '已发货',
self::Delivered => '已送达',
self::Cancelled => '已取消',
};
}
}
class Order
{
public function __construct(
public int $id,
public OrderStatus $status = OrderStatus::Pending
) {}
public function transitionTo(OrderStatus $newStatus): void
{
if (!$this->status->canTransitionTo($newStatus)) {
throw new RuntimeException(
"不允许从 {$this->status->label()} 转换到 {$newStatus->label()}"
);
}
$this->status = $newStatus;
echo "订单 #{$this->id} 状态更新为: {$newStatus->label()}n";
}
}
// 使用示例
$order = new Order(1001);
$order->transitionTo(OrderStatus::Paid); // 成功
$order->transitionTo(OrderStatus::Shipped); // 成功
$order->transitionTo(OrderStatus::Delivered); // 成功
// $order->transitionTo(OrderStatus::Cancelled); // 抛异常:不允许从已送达转换到已取消
5. 实战案例二:业务规则引擎
使用枚举定义不同的促销规则,通过模式匹配计算折扣。
enum CouponType: string
{
case Percentage = 'percentage'; // 百分比折扣
case FixedAmount = 'fixed'; // 固定金额
case FreeShipping = 'free_ship'; // 免运费
public function calculateDiscount(float $orderTotal, float $value): float
{
return match($this) {
self::Percentage => $orderTotal * ($value / 100),
self::FixedAmount => min($value, $orderTotal), // 不超过订单总额
self::FreeShipping => 0, // 运费由外部计算
};
}
}
class Coupon
{
public function __construct(
public string $code,
public CouponType $type,
public float $value
) {}
public function apply(float $orderTotal): array
{
$discount = $this->type->calculateDiscount($orderTotal, $this->value);
return [
'code' => $this->code,
'type' => $this->type->name,
'original_total' => $orderTotal,
'discount' => round($discount, 2),
'final_total' => round($orderTotal - $discount, 2),
];
}
}
// 使用示例
$coupon1 = new Coupon('SAVE20', CouponType::Percentage, 20);
$coupon2 = new Coupon('FLAT50', CouponType::FixedAmount, 50);
print_r($coupon1->apply(200)); // 折扣: 40, 最终: 160
print_r($coupon2->apply(200)); // 折扣: 50, 最终: 150
6. 实战案例三:数据验证器
使用枚举定义验证规则,结合模式匹配构建灵活的验证器。
enum ValidationRule: string
{
case Required = 'required';
case Email = 'email';
case MinLength = 'min_length';
case MaxLength = 'max_length';
case Numeric = 'numeric';
public function validate(mixed $value, mixed $parameter = null): ?string
{
return match($this) {
self::Required => empty($value) ? '此字段不能为空' : null,
self::Email => filter_var($value, FILTER_VALIDATE_EMAIL) ? null : '无效的邮箱格式',
self::MinLength => strlen($value) strlen($value) > $parameter ? "长度不能超过{$parameter}个字符" : null,
self::Numeric => !is_numeric($value) ? '必须是数字' : null,
};
}
}
class Validator
{
private array $errors = [];
public function validate(array $data, array $rules): bool
{
$this->errors = [];
foreach ($rules as $field => $fieldRules) {
$value = $data[$field] ?? null;
foreach ($fieldRules as $rule) {
$ruleInstance = $rule['rule'];
$parameter = $rule['parameter'] ?? null;
$error = $ruleInstance->validate($value, $parameter);
if ($error !== null) {
$this->errors[$field][] = $error;
}
}
}
return empty($this->errors);
}
public function getErrors(): array
{
return $this->errors;
}
}
// 使用示例
$validator = new Validator();
$data = ['name' => 'John', 'email' => 'invalid-email', 'age' => 'abc'];
$rules = [
'name' => [
['rule' => ValidationRule::Required],
['rule' => ValidationRule::MinLength, 'parameter' => 2],
],
'email' => [
['rule' => ValidationRule::Required],
['rule' => ValidationRule::Email],
],
'age' => [
['rule' => ValidationRule::Numeric],
],
];
if (!$validator->validate($data, $rules)) {
print_r($validator->getErrors());
// 输出: email => 无效的邮箱格式, age => 必须是数字
}
7. 枚举与模式匹配的性能优势
| 指标 | 传统常量+switch | 枚举+match |
|---|---|---|
| 类型安全 | 无(字符串/整数易混淆) | 强类型 |
| IDE支持 | 有限 | 自动补全、重构支持 |
| 穷尽性检查 | 无(遗漏case不报错) | match 要求覆盖所有可能 |
| 可维护性 | 差(散落的常量) | 好(集中定义) |
8. 最佳实践总结
- 优先使用枚举代替常量:枚举提供类型安全和自文档能力
- match 代替 switch:match 返回值、穷尽性检查、更简洁
- 枚举内聚业务逻辑:将相关方法定义在枚举内部
- 结合模式匹配做状态机:确保状态转换合法性
- 利用回退枚举与数据库交互:使用 value 属性存储到数据库
// 数据库交互示例
enum UserRole: string
{
case Admin = 'admin';
case Editor = 'editor';
case Viewer = 'viewer';
public static function fromDatabase(string $value): self
{
return self::from($value); // 自动验证
}
}
// 从数据库获取角色
$role = UserRole::fromDatabase('admin');
echo $role->name; // Admin
9. 总结
通过本文的案例,你掌握了 PHP 8.4 枚举和模式匹配的核心技术:
- 枚举的定义(纯枚举、回退枚举)
- 枚举方法、状态转换验证
- match 表达式的高级用法
- 状态机、规则引擎、验证器实战
- 性能对比和最佳实践
枚举和模式匹配让 PHP 代码更加类型安全、表达力更强。现在就开始在你的项目中应用这些特性吧!
本文原创,基于 PHP 8.4。所有代码均在 PHP 8.4 环境中测试通过。

