枚举 Trait
概述
PHP 8.1+ 允许在枚举中使用 Trait(use trait),将通用的枚举方法抽取到可复用的 Trait 中。这解决了多个枚举之间存在相同方法逻辑时代码重复的问题。枚举中使用 Trait 的方式与类中使用 Trait 完全一致。
版本要求
枚举中使用 Trait 是 PHP 8.1+ 专属特性。
基础概念
为什么枚举需要 Trait
枚举不能被继承(隐式 final),因此无法通过抽象基类来共享方法。Trait 提供了一种在多个枚举之间复用方法的机制。
枚举 Trait 的限制
- Trait 中的方法不能向枚举添加属性
- Trait 中可以使用
$this引用枚举实例 - Trait 可以包含抽象方法,由枚举实现
语法与代码
基本用法
php
<?php
declare(strict_types=1);
trait HasLabel
{
abstract public function label(): string;
public function description(): string
{
return "This is {$this->label()}";
}
}
enum Status
{
use HasLabel;
case Active;
case Inactive;
public function label(): string
{
return match ($this) {
self::Active => '活跃',
self::Inactive => '不活跃',
};
}
}
echo Status::Active->label(); // 活跃
echo Status::Active->description(); // This is 活跃多个枚举共享 Trait
php
<?php
declare(strict_types=1);
trait HasColor
{
abstract public function color(): string;
public function colorHtml(): string
{
return "<span style=\"color: {$this->color()}\">{$this->name}</span>";
}
}
trait HasOptions
{
public static function options(): array
{
return array_column(self::cases(), 'name');
}
}
enum TrafficLight
{
use HasColor;
use HasOptions;
case Red;
case Yellow;
case Green;
public function color(): string
{
return match ($this) {
self::Red => '#FF0000',
self::Yellow => '#FFFF00',
self::Green => '#00FF00',
};
}
}
enum AlertLevel
{
use HasColor;
use HasOptions;
case Info;
case Warning;
case Error;
public function color(): string
{
return match ($this) {
self::Info => '#3B82F6',
self::Warning => '#F59E0B',
self::Error => '#EF4444',
};
}
}
echo TrafficLight::Red->colorHtml();
echo AlertLevel::Error->colorHtml();
print_r(TrafficLight::options());Backed Enum 中使用 Trait
php
<?php
declare(strict_types=1);
trait Selectable
{
public static function selectOptions(): array
{
$options = [];
foreach (self::cases() as $case) {
$options[$case->value] = $case->name;
}
return $options;
}
public static function toSelectArray(): array
{
return array_map(
fn(self $case) => ['value' => $case->value, 'label' => $case->name],
self::cases()
);
}
}
enum PaymentStatus: string
{
use Selectable;
case Pending = 'pending';
case Completed = 'completed';
case Failed = 'failed';
case Refunded = 'refunded';
}
// 获取下拉选项
$options = PaymentStatus::selectOptions();
// ['pending' => 'Pending', 'completed' => 'Completed', ...]Trait 中的抽象方法
php
<?php
declare(strict_types=1);
trait Enumerable
{
abstract public function value(): mixed;
public static function all(): array
{
return self::cases();
}
public static function count(): int
{
return count(self::cases());
}
public static function first(): static
{
return self::cases()[0];
}
public static function last(): static
{
$cases = self::cases();
return $cases[array_key_last($cases)];
}
}
enum Plan
{
use Enumerable;
case Free;
case Basic;
case Premium;
public function value(): mixed
{
return $this->name;
}
}
echo Plan::count(); // 3
echo Plan::first()->name; // "Free"
echo Plan::last()->name; // "Premium"Trait 方法与枚举方法冲突
php
<?php
declare(strict_types=1);
trait CommonMethods
{
public function name(): string
{
return $this->name; // 与枚举内置 name 属性冲突
}
}
enum BadEnum
{
use CommonMethods; // Fatal error: Trait method name conflicts
case A;
}冲突处理
如果 Trait 方法与枚举内置属性或方法冲突,需要使用 insteadof 或 as 操作符解决,或者重命名 Trait 方法。
详细说明
Trait 在枚举中的适用场景
- 通用查询方法:如
all()、count()、options() - 通用显示方法:如
label()、color()、icon() - 通用转换方法:如
toArray()、toJson() - 通用验证方法:如
isValid()、isDefault()
Trait 不能向枚举添加属性
php
<?php
declare(strict_types=1);
trait WithProperties
{
// 枚举不能有属性,以下代码在枚举中使用会报错
protected string $label = '';
}
enum BrokenEnum
{
use WithProperties; // Fatal error if trait has properties
case A;
}Trait 组合多个枚举的能力
php
<?php
declare(strict_types=1);
trait HasValidation
{
public function isValid(): bool
{
return in_array($this, self::cases(), true);
}
}
trait HasSerialization
{
public function serialize(): string
{
return $this instanceof \BackedEnum
? $this->value
: $this->name;
}
public static function deserialize(string $value): ?static
{
if (self::cases()[0] instanceof \BackedEnum) {
return self::tryFrom($value);
}
return null;
}
}
enum ErrorCode: int
{
use HasValidation;
use HasSerialization;
case None = 0;
case NotFound = 404;
case ServerError = 500;
}实战示例
场景一:通用枚举转换 Trait
php
<?php
declare(strict_types=1);
trait BackedEnumHelper
{
/**
* 获取所有值的数组
* @return array<int|string>
*/
public static function values(): array
{
return array_map(fn(self $case) => $case->value, self::cases());
}
/**
* 值到名称的映射
* @return array<int|string, string>
*/
public static function valueToNameMap(): array
{
$map = [];
foreach (self::cases() as $case) {
$map[$case->value] = $case->name;
}
return $map;
}
/**
* 检查值是否合法
*/
public static function isValidValue(int|string $value): bool
{
return in_array($value, self::values(), true);
}
}
enum UserType: string
{
use BackedEnumHelper;
case Customer = 'customer';
case Merchant = 'merchant';
case Admin = 'admin';
}
// 使用
var_dump(UserType::values()); // ['customer', 'merchant', 'admin']
var_dump(UserType::isValidValue('xxx')); // false场景二:本地化 Trait
php
<?php
declare(strict_types=1);
trait HasLocalizedLabels
{
abstract protected function labelMap(): array;
public function label(string $locale = 'zh'): string
{
$map = $this->labelMap();
return $map[$locale][$this->name] ?? $this->name;
}
}
enum OrderState: string
{
use HasLocalizedLabels;
case New = 'new';
case Processing = 'processing';
case Shipped = 'shipped';
case Completed = 'completed';
protected function labelMap(): array
{
return [
'zh' => [
'New' => '新订单',
'Processing' => '处理中',
'Shipped' => '已发货',
'Completed' => '已完成',
],
'en' => [
'New' => 'New Order',
'Processing' => 'Processing',
'Shipped' => 'Shipped',
'Completed' => 'Completed',
],
];
}
}
echo OrderState::New->label(); // 新订单
echo OrderState::New->label('en'); // New Order注意事项
注意事项
- 枚举不能被继承,Trait 是共享枚举方法的唯一方式
- Trait 不能向枚举添加属性(properties)
- Trait 方法名不能与枚举内置属性/方法冲突(如
name、value) - 可以使用
as操作符为 Trait 方法起别名来避免冲突
小贴士
- 将高频使用的枚举工具方法抽取为 Trait
- 使用抽象方法确保枚举实现 Trait 所需的行为
- Backed Enum 和纯枚举可以使用不同的 Trait
最佳实践
1. 保持 Trait 专注单一职责
php
<?php
declare(strict_types=1);
// 推荐 - 单一职责的 Trait
trait HasLabel { /* ... */ }
trait HasColor { /* ... */ }
// 不推荐 - 职责过多的 Trait
trait Everything { /* ... */ }2. 使用抽象方法约束枚举实现
php
<?php
declare(strict_types=1);
trait Sortable
{
abstract public function sortOrder(): int;
public static function sorted(): array
{
$cases = self::cases();
usort($cases, fn($a, $b) => $a->sortOrder() <=> $b->sortOrder());
return $cases;
}
}