免费资源下载
原创技术教程 | 更新时间:2023年10月
一、枚举类型带来的变革
在PHP 8.1之前,开发者通常使用类常量或数组来模拟枚举,但这种方式缺乏类型安全性,容易导致运行时错误。PHP 8.1引入的枚举类型(Enumerations)彻底改变了这一局面,提供了真正的类型安全枚举支持。
传统方式的缺陷:
// 传统方式 - 类常量
class OrderStatus {
const PENDING = 'pending';
const PROCESSING = 'processing';
const SHIPPED = 'shipped';
const DELIVERED = 'delivered';
const CANCELLED = 'cancelled';
}
// 问题:无法限制传入的值
function updateOrderStatus(string $status) {
// 可能传入无效状态值
if (!in_array($status, [
OrderStatus::PENDING,
OrderStatus::PROCESSING,
// 容易遗漏检查
])) {
throw new InvalidArgumentException('无效状态');
}
}
二、PHP枚举基础语法
PHP枚举是一种特殊的类,使用enum关键字定义,只能包含”实例”(case),不能直接实例化。
纯枚举(Pure Enums)定义:
enum OrderStatus {
case PENDING;
case PROCESSING;
case SHIPPED;
case DELIVERED;
case CANCELLED;
}
// 使用示例
$status = OrderStatus::PENDING;
// 类型安全的方法参数
function processOrder(OrderStatus $status): void {
// 无需验证,参数一定是有效的枚举值
echo "处理订单状态: " . $status->name;
}
// 调用 - 只能传入枚举值
processOrder(OrderStatus::SHIPPED); // 正确
// processOrder('shipped'); // 类型错误!
三、带值的枚举(Backed Enums)
带值枚举允许为每个case指定标量值(string或int),便于数据库存储和序列化。
enum OrderStatus: string {
case PENDING = 'pending';
case PROCESSING = 'processing';
case SHIPPED = 'shipped';
case DELIVERED = 'delivered';
case CANCELLED = 'cancelled';
// 可以添加方法
public function getDescription(): string {
return match($this) {
self::PENDING => '订单已创建,等待处理',
self::PROCESSING => '订单正在处理中',
self::SHIPPED => '商品已发货',
self::DELIVERED => '商品已送达',
self::CANCELLED => '订单已取消',
};
}
// 静态方法:从值创建枚举
public static function fromValue(string $value): self {
return self::from($value);
}
// 安全转换方法
public static function tryFromValue(?string $value): ?self {
return $value ? self::tryFrom($value) : null;
}
}
// 使用示例
$status = OrderStatus::from('pending');
echo $status->getDescription(); // 输出:订单已创建,等待处理
echo $status->value; // 输出:pending
四、实战案例:电商订单状态系统
下面我们构建一个完整的电商订单状态管理系统,展示枚举在实际项目中的应用。
1. 定义订单状态枚举
enum OrderStatus: string {
case DRAFT = 'draft'; // 草稿
case PENDING_PAYMENT = 'pending_payment'; // 待支付
case PAID = 'paid'; // 已支付
case PROCESSING = 'processing'; // 处理中
case READY_TO_SHIP = 'ready_to_ship'; // 待发货
case SHIPPED = 'shipped'; // 已发货
case IN_TRANSIT = 'in_transit'; // 运输中
case OUT_FOR_DELIVERY = 'out_for_delivery'; // 派送中
case DELIVERED = 'delivered'; // 已送达
case CONFIRMED = 'confirmed'; // 已确认收货
case CANCELLED = 'cancelled'; // 已取消
case REFUNDED = 'refunded'; // 已退款
case DISPUTED = 'disputed'; // 争议中
// 获取下一步可能的状态
public function getNextPossibleStatuses(): array {
return match($this) {
self::DRAFT => [self::PENDING_PAYMENT],
self::PENDING_PAYMENT => [self::PAID, self::CANCELLED],
self::PAID => [self::PROCESSING, self::REFUNDED],
self::PROCESSING => [self::READY_TO_SHIP, self::DISPUTED],
self::READY_TO_SHIP => [self::SHIPPED],
self::SHIPPED => [self::IN_TRANSIT],
self::IN_TRANSIT => [self::OUT_FOR_DELIVERY],
self::OUT_FOR_DELIVERY => [self::DELIVERED],
self::DELIVERED => [self::CONFIRMED, self::DISPUTED],
self::CONFIRMED => [], // 最终状态
self::CANCELLED => [], // 最终状态
self::REFUNDED => [], // 最终状态
self::DISPUTED => [self::REFUNDED, self::CANCELLED],
};
}
// 检查状态转换是否有效
public function canTransitionTo(self $newStatus): bool {
return in_array($newStatus, $this->getNextPossibleStatuses());
}
// 获取状态组
public function getStatusGroup(): string {
return match($this) {
self::DRAFT,
self::PENDING_PAYMENT => 'pre_processing',
self::PAID,
self::PROCESSING,
self::READY_TO_SHIP => 'processing',
self::SHIPPED,
self::IN_TRANSIT,
self::OUT_FOR_DELIVERY => 'shipping',
self::DELIVERED,
self::CONFIRMED => 'completed',
self::CANCELLED,
self::REFUNDED,
self::DISPUTED => 'cancelled',
};
}
}
2. 订单实体类
class Order {
private string $id;
private OrderStatus $status;
private DateTimeImmutable $createdAt;
private ?DateTimeImmutable $statusChangedAt = null;
private array $statusHistory = [];
public function __construct(string $id) {
$this->id = $id;
$this->status = OrderStatus::DRAFT;
$this->createdAt = new DateTimeImmutable();
$this->recordStatusChange($this->status);
}
public function changeStatus(OrderStatus $newStatus): void {
if (!$this->status->canTransitionTo($newStatus)) {
throw new InvalidStateTransitionException(
"无法从 {$this->status->value} 转换到 {$newStatus->value}"
);
}
$this->status = $newStatus;
$this->statusChangedAt = new DateTimeImmutable();
$this->recordStatusChange($newStatus);
}
private function recordStatusChange(OrderStatus $status): void {
$this->statusHistory[] = [
'status' => $status->value,
'timestamp' => (new DateTimeImmutable())->format('Y-m-d H:i:s'),
'description' => $status->getDescription(),
];
}
public function getCurrentStatus(): OrderStatus {
return $this->status;
}
public function getStatusHistory(): array {
return $this->statusHistory;
}
// 序列化为数组(用于API响应)
public function toArray(): array {
return [
'id' => $this->id,
'status' => [
'value' => $this->status->value,
'name' => $this->status->name,
'description' => $this->status->getDescription(),
'group' => $this->status->getStatusGroup(),
],
'created_at' => $this->createdAt->format('Y-m-d H:i:s'),
'status_changed_at' => $this->statusChangedAt?->format('Y-m-d H:i:s'),
'can_transition_to' => array_map(
fn($status) => $status->value,
$this->status->getNextPossibleStatuses()
),
];
}
}
3. 状态转换处理器
class OrderStatusTransitionHandler {
private Order $order;
public function __construct(Order $order) {
$this->order = $order;
}
public function handlePaymentReceived(): void {
$this->order->changeStatus(OrderStatus::PAID);
$this->sendNotification('payment_received');
}
public function handleShippingStarted(): void {
$this->order->changeStatus(OrderStatus::SHIPPED);
$this->sendNotification('order_shipped');
}
public function handleDeliveryConfirmed(): void {
$this->order->changeStatus(OrderStatus::CONFIRMED);
$this->sendNotification('delivery_confirmed');
}
public function handleCancellationRequest(string $reason): void {
$currentStatus = $this->order->getCurrentStatus();
// 检查是否可以取消
if (!$currentStatus->canTransitionTo(OrderStatus::CANCELLED)) {
throw new InvalidOperationException(
"当前状态 {$currentStatus->value} 无法取消订单"
);
}
$this->order->changeStatus(OrderStatus::CANCELLED);
$this->logCancellation($reason);
$this->sendNotification('order_cancelled');
}
private function sendNotification(string $type): void {
// 发送通知的逻辑
// 可以使用观察者模式或事件系统
}
private function logCancellation(string $reason): void {
// 记录取消原因
}
}
4. 使用示例
// 创建新订单
$order = new Order('ORD-2023-001');
// 模拟订单流程
$handler = new OrderStatusTransitionHandler($order);
try {
// 客户支付
$handler->handlePaymentReceived();
// 商家发货
$handler->handleShippingStarted();
// 客户确认收货
$handler->handleDeliveryConfirmed();
// 输出订单信息
echo "订单状态流程完成n";
print_r($order->toArray());
// 获取所有可能的订单状态
echo "n所有订单状态:n";
foreach (OrderStatus::cases() as $status) {
echo sprintf(
"%s: %s (%s)n",
$status->name,
$status->value,
$status->getDescription()
);
}
} catch (InvalidStateTransitionException $e) {
echo "状态转换错误: " . $e->getMessage();
}
五、枚举的高级特性应用
1. 枚举实现接口
interface StatusInterface {
public function getColor(): string;
public function isFinal(): bool;
}
enum OrderStatus: string implements StatusInterface {
case PENDING = 'pending';
case COMPLETED = 'completed';
case CANCELLED = 'cancelled';
public function getColor(): string {
return match($this) {
self::PENDING => 'yellow',
self::COMPLETED => 'green',
self::CANCELLED => 'red',
};
}
public function isFinal(): bool {
return match($this) {
self::PENDING => false,
self::COMPLETED, self::CANCELLED => true,
};
}
}
2. 枚举中的常量和方法
enum PaymentMethod: string {
case CREDIT_CARD = 'credit_card';
case PAYPAL = 'paypal';
case BANK_TRANSFER = 'bank_transfer';
case CRYPTO = 'crypto';
// 枚举常量
public const ALLOWED_METHODS = [
self::CREDIT_CARD,
self::PAYPAL,
self::BANK_TRANSFER,
];
// 静态方法
public static function getAvailableMethods(): array {
return array_filter(
self::cases(),
fn($method) => in_array($method, self::ALLOWED_METHODS)
);
}
// 实例方法
public function getProcessingFee(): float {
return match($this) {
self::CREDIT_CARD => 0.029, // 2.9%
self::PAYPAL => 0.024, // 2.4%
self::BANK_TRANSFER => 0.01, // 1%
self::CRYPTO => 0.005, // 0.5%
};
}
}
六、最佳实践与性能考量
最佳实践:
- 使用带值枚举存储数据:需要数据库存储或序列化时,使用
Backed Enums - 利用match表达式:枚举与PHP 8.0的match表达式完美配合
- 添加业务逻辑方法:将相关业务逻辑封装在枚举方法中
- 实现状态机模式:枚举非常适合实现有限状态机
- 使用tryFrom处理未知值:避免使用
from()抛出异常
性能考量:
// 性能优化:缓存枚举实例
enum ProductCategory: int {
case ELECTRONICS = 1;
case CLOTHING = 2;
case BOOKS = 3;
private static ?array $cache = null;
public static function getAllCategories(): array {
if (self::$cache === null) {
self::$cache = array_combine(
array_column(self::cases(), 'value'),
self::cases()
);
}
return self::$cache;
}
public static function fromId(int $id): ?self {
return self::getAllCategories()[$id] ?? null;
}
}
// 数据库查询优化示例
class OrderRepository {
public function findOrdersByStatus(OrderStatus $status): array {
// 使用枚举值进行查询
$query = "SELECT * FROM orders WHERE status = :status";
$params = ['status' => $status->value];
// 执行查询...
return $results;
}
public function findOrdersByStatusGroup(string $group): array {
// 使用枚举方法过滤
$allStatuses = OrderStatus::cases();
$statusesInGroup = array_filter(
$allStatuses,
fn($status) => $status->getStatusGroup() === $group
);
$statusValues = array_map(
fn($status) => $status->value,
$statusesInGroup
);
$placeholders = implode(',', array_fill(0, count($statusValues), '?'));
$query = "SELECT * FROM orders WHERE status IN ($placeholders)";
// 执行查询...
return $results;
}
}
测试策略:
class OrderStatusTest extends TestCase {
public function testStatusTransitions(): void {
$order = new Order('TEST-001');
// 测试有效转换
$order->changeStatus(OrderStatus::PAID);
$this->assertEquals(OrderStatus::PAID, $order->getCurrentStatus());
// 测试无效转换
$this->expectException(InvalidStateTransitionException::class);
$order->changeStatus(OrderStatus::DRAFT); // 不能从PAID回到DRAFT
}
public function testEnumMethods(): void {
$status = OrderStatus::PENDING;
$this->assertEquals('pending', $status->value);
$this->assertEquals('订单已创建,等待处理', $status->getDescription());
$this->assertTrue($status->canTransitionTo(OrderStatus::PAID));
$this->assertFalse($status->canTransitionTo(OrderStatus::DELIVERED));
}
public function testAllStatusesCoverage(): void {
// 确保所有状态都有描述
foreach (OrderStatus::cases() as $status) {
$this->assertNotEmpty($status->getDescription());
$this->assertNotEmpty($status->getStatusGroup());
}
}
}
总结
PHP枚举类型为开发者提供了强大的类型安全工具,特别适合状态管理、配置选项和有限值集合的场景。通过本文的电商订单状态系统案例,我们展示了如何:
- 使用枚举替代传统的常量定义
- 实现类型安全的状态转换
- 在枚举中封装业务逻辑
- 构建可维护的状态机系统
- 优化数据库查询和API响应
枚举不仅提高了代码的可读性和可维护性,还通过编译时检查减少了运行时错误。建议在PHP 8.1+项目中积极采用枚举,特别是处理状态管理、选项配置等场景。

