Skip to content

枚举方法

概述

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
  • 枚举不能被继承,selfstatic 在枚举中行为一致
  • 实现接口时,必须实现所有接口定义的方法

小贴士

  • 将业务逻辑放在枚举方法中,而非散落在各处的 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);
}

参考链接