枚举方法
概述
PHP 枚举可以定义实例方法和静态方法,使得枚举不仅仅是值的集合,还能携带行为。枚举方法可以使用 match 表达式根据不同的 case 返回不同的结果,从而实现类似策略模式的效果。此外,枚举还可以实现接口,进一步扩展其能力。
版本要求
枚举方法是 PHP 8.1+ 专属特性。
基础概念
枚举可以定义的方法类型
| 方法类型 | 说明 | 示例 |
|---|---|---|
| 实例方法 | 通过枚举实例调用 | $status->label() |
| 静态方法 | 通过枚举类调用 | Status::fromCode(200) |
| 接口方法 | 实现接口定义的方法 | label(), value() |
语法与代码
实例方法
php
<?php
declare(strict_types=1);
enum SubscriptionPlan
{
case Free;
case Basic;
case Premium;
case Enterprise;
public function price(): int
{
return match ($this) {
self::Free => 0,
self::Basic => 29,
self::Premium => 99,
self::Enterprise => 299,
};
}
public function maxProjects(): int
{
return match ($this) {
self::Free => 3,
self::Basic => 10,
self::Premium => 50,
self::Enterprise => PHP_INT_MAX,
};
}
public function label(): string
{
return match ($this) {
self::Free => '免费版',
self::Basic => '基础版',
self::Premium => '专业版',
self::Enterprise => '企业版',
};
}
public function isPaid(): bool
{
return $this->price() > 0;
}
}
$plan = SubscriptionPlan::Premium;
echo $plan->label(); // 专业版
echo $plan->price(); // 99
echo $plan->isPaid(); // true静态方法
php
<?php
declare(strict_types=1);
enum HttpStatusCode: int
{
case Ok = 200;
case Created = 201;
case NoContent = 204;
case BadRequest = 400;
case Unauthorized = 401;
case Forbidden = 403;
case NotFound = 404;
case InternalServerError = 500;
public static function isSuccess(int $code): bool
{
return $code >= 200 && $code < 300;
}
public static function isClientError(int $code): bool
{
return $code >= 400 && $code < 500;
}
public static function isServerError(int $code): bool
{
return $code >= 500 && $code < 600;
}
public static function fromCode(int $code): ?self
{
return self::tryFrom($code);
}
public function category(): string
{
return match (true) {
self::isSuccess($this->value) => '成功',
self::isClientError($this->value) => '客户端错误',
self::isServerError($this->value) => '服务器错误',
default => '其他',
};
}
}枚举实现接口
php
<?php
declare(strict_types=1);
interface Describable
{
public function label(): string;
public function description(): string;
public function icon(): string;
}
enum TaskPriority implements Describable
{
case Low;
case Medium;
case High;
case Urgent;
public function label(): string
{
return match ($this) {
self::Low => '低优先级',
self::Medium => '中优先级',
self::High => '高优先级',
self::Urgent => '紧急',
};
}
public function description(): string
{
return match ($this) {
self::Low => '有空闲时处理',
self::Medium => '按计划处理',
self::High => '优先处理',
self::Urgent => '立即处理',
};
}
public function icon(): string
{
return match ($this) {
self::Low => 'down-arrow',
self::Medium => 'minus',
self::High => 'up-arrow',
self::Urgent => 'fire',
};
}
public function color(): string // 非接口方法
{
return match ($this) {
self::Low => '#6B7280',
self::Medium => '#F59E0B',
self::High => '#EF4444',
self::Urgent => '#DC2626',
};
}
}实现多个接口
php
<?php
declare(strict_types=1);
interface Arrayable
{
public function toArray(): array;
}
interface Jsonable
{
public function toJson(): string;
}
enum Language: string implements Arrayable, Jsonable
{
case Php = 'php';
case Python = 'python';
case JavaScript = 'javascript';
case Go = 'go';
case Rust = 'rust';
public function label(): string
{
return match ($this) {
self::Php => 'PHP',
self::Python => 'Python',
self::JavaScript => 'JavaScript',
self::Go => 'Go',
self::Rust => 'Rust',
};
}
public function toArray(): array
{
return [
'value' => $this->value,
'label' => $this->label(),
];
}
public function toJson(): string
{
return json_encode($this->toArray(), JSON_THROW_ON_ERROR);
}
}详细说明
self 与 static 的区别
php
<?php
declare(strict_types=1);
enum BaseType
{
case A;
case B;
public function getSelf(): self
{
// self 总是指向当前枚举类
return self::A;
}
}
// self 和 static 在枚举中行为一致
// 因为枚举不能被继承(final)方法中访问枚举值
php
<?php
declare(strict_types=1);
enum DiscountType: string
{
case Percentage = 'percentage';
case Fixed = 'fixed';
public function calculate(int $originalPrice, int $discountValue): int
{
return match ($this) {
self::Percentage => (int) ($originalPrice * (1 - $discountValue / 100)),
self::Fixed => max(0, $originalPrice - $discountValue),
};
}
public function description(int $discountValue): string
{
return match ($this) {
self::Percentage => "打 {$discountValue}% 折",
self::Fixed => "减 {$discountValue} 元",
};
}
}
// 使用
$discount = DiscountType::Percentage;
echo $discount->calculate(100, 20); // 80
echo $discount->description(20); // 打 20% 折静态工厂方法
php
<?php
declare(strict_types=1);
enum Environment: string
{
case Local = 'local';
case Development = 'development';
case Staging = 'staging;
case Production = 'production';
public static function fromEnv(): self
{
$env = getenv('APP_ENV') ?: 'local';
return self::tryFrom($env) ?? self::Local;
}
public function isProduction(): bool
{
return $this === self::Production;
}
public function isDebug(): bool
{
return in_array($this, [self::Local, self::Development], true);
}
public function shouldShowErrors(): bool
{
return $this->isDebug();
}
}实战示例
场景一:带行为的业务枚举
php
<?php
declare(strict_types=1);
enum InvoiceStatus: string
{
case Draft = 'draft';
case Sent = 'sent';
case Paid = 'paid';
case Overdue = 'overdue';
case Cancelled = 'cancelled';
public function label(): string
{
return match ($this) {
self::Draft => '草稿',
self::Sent => '已发送',
self::Paid => '已支付',
self::Overdue => '逾期',
self::Cancelled => '已取消',
};
}
public function canTransitionTo(self $target): bool
{
$transitions = [
self::Draft->value => [self::Sent, self::Cancelled],
self::Sent->value => [self::Paid, self::Overdue, self::Cancelled],
self::Overdue->value => [self::Paid, self::Cancelled],
];
return in_array($target, $transitions[$this->value] ?? [], true);
}
public function color(): string
{
return match ($this) {
self::Draft => '#6B7280',
self::Sent => '#3B82F6',
self::Paid => '#10B981',
self::Overdue => '#EF4444',
self::Cancelled => '#9CA3AF',
};
}
}场景二:接口驱动的多枚举协作
php
<?php
declare(strict_types=1);
interface Localizable
{
public function label(string $locale = 'zh'): string;
}
enum Country: string implements Localizable
{
case Cn = 'CN';
case Us = 'US';
case Jp = 'JP';
case Kr = 'KR';
public function label(string $locale = 'zh'): string
{
$labels = [
'zh' => [
self::Cn->value => '中国',
self::Us->value => '美国',
self::Jp->value => '日本',
self::Kr->value => '韩国',
],
'en' => [
self::Cn->value => 'China',
self::Us->value => 'United States',
self::Jp->value => 'Japan',
self::Kr->value => 'South Korea',
],
];
return $labels[$locale][$this->value] ?? $this->name;
}
}
enum Currency: string implements Localizable
{
case Cny = 'CNY';
case Usd = 'USD';
case Jpy = 'JPY';
case Krw = 'KRW';
public function label(string $locale = 'zh'): string
{
return match ($this) {
self::Cny => $locale === 'zh' ? '人民币' : 'Chinese Yuan',
self::Usd => $locale === 'zh' ? '美元' : 'US Dollar',
self::Jpy => $locale === 'zh' ? '日元' : 'Japanese Yen',
self::Krw => $locale === 'zh' ? '韩元' : 'Korean Won',
};
}
}注意事项
注意事项
- 枚举不能包含属性,但方法可以返回计算值
- 枚举方法可以使用
$this引用当前 case - 枚举不能被继承,
self和static在枚举中行为一致 - 实现接口时,必须实现所有接口定义的方法
小贴士
- 将业务逻辑放在枚举方法中,而非散落在各处的 switch/if 语句
- 使用接口让不同的枚举具有一致的行为契约
- 静态方法适合作为工厂方法或工具方法
最佳实践
1. 将行为绑定到枚举
php
<?php
declare(strict_types=1);
enum FileType: string
{
case Image = 'image';
case Video = 'video';
case Document = 'document';
// 行为绑定到枚举,而非外部
public function maxUploadSizeMb(): int
{
return match ($this) {
self::Image => 10,
self::Video => 100,
self::Document => 50,
};
}
}2. 使用接口实现多态
php
<?php
declare(strict_types=1);
// 通过接口统一不同枚举的行为
function displayLabel(Localizable $item, string $locale = 'zh'): string
{
return $item->label($locale);
}