Skip to content

枚举基础

概述

PHP 8.1 引入的 enum 关键字用于定义枚举类型。枚举通过 case 关键字定义各个可能的值。纯枚举(Pure Enum)不携带标量值,仅作为类型安全的标识符使用。每个枚举 case 都是该枚举类的单例实例,自动实现 UnitEnum 接口。

基础概念

enum 关键字

enum 用于声明枚举类型,语法类似于类声明,但有以下限制:

  • 枚举隐式为 final,不能被继承
  • 枚举不能使用 new 实例化
  • 枚举只能包含 case、方法、常量和 trait

UnitEnum 接口

所有纯枚举自动实现 UnitEnum 接口,该接口提供:

  • cases(): array — 返回所有 case 实例的数组
  • name: string — 只读属性,返回 case 的名称

语法与代码

定义纯枚举

php
<?php

declare(strict_types=1);

enum Suit
{
    case Hearts;
    case Diamonds;
    case Clubs;
    case Spades;
}

// 使用枚举
$card = Suit::Hearts;
echo $card->name;  // "Hearts"

// 遍历所有 case
foreach (Suit::cases() as $suit) {
    echo $suit->name . PHP_EOL;
}

枚举实例是单例

php
<?php

declare(strict_types=1);

enum Season
{
    case Spring;
    case Summer;
    case Autumn;
    case Winter;
}

$a = Season::Spring;
$b = Season::Spring;
$c = Season::Summer;

var_dump($a === $b);     // bool(true) - 同一个 case
var_dump($a === $c);     // bool(false) - 不同的 case
var_dump($a instanceof Season);  // bool(true)

在方法参数中使用枚举

php
<?php

declare(strict_types=1);

enum LogLevel
{
    case Debug;
    case Info;
    case Warning;
    case Error;
    case Critical;
}

class Logger
{
    public function log(LogLevel $level, string $message): void
    {
        $prefix = match ($level) {
            LogLevel::Debug => '[DEBUG]',
            LogLevel::Info => '[INFO]',
            LogLevel::Warning => '[WARNING]',
            LogLevel::Error => '[ERROR]',
            LogLevel::Critical => '[CRITICAL]',
        };

        echo "{$prefix} {$message}" . PHP_EOL;
    }
}

$logger = new Logger();
$logger->log(LogLevel::Info, 'System started');
$logger->log(LogLevel::Error, 'Database connection failed');

在枚举中定义方法

php
<?php

declare(strict_types=1);

enum HttpStatus
{
    case Ok;
    case NotFound;
    caseServerError;
    case Unauthorized;

    public function code(): int
    {
        return match ($this) {
            self::Ok => 200,
            self::NotFound => 404,
            self::ServerError => 500,
            self::Unauthorized => 401,
        };
    }

    public function label(): string
    {
        return match ($this) {
            self::Ok => '成功',
            self::NotFound => '未找到',
            self::ServerError => '服务器错误',
            self::Unauthorized => '未授权',
        };
    }

    public function isSuccess(): bool
    {
        return $this === self::Ok;
    }
}

$status = HttpStatus::NotFound;
echo $status->code();    // 404
echo $status->label();   // 未找到
echo $status->isSuccess();  // false

静态方法

php
<?php

declare(strict_types=1);

enum Weekday
{
    case Monday;
    case Tuesday;
    case Wednesday;
    case Thursday;
    case Friday;
    case Saturday;
    case Sunday;

    public static function today(): self
    {
        $day = (int) date('N');  // 1 (Monday) to 7 (Sunday)
        return self::cases()[$day - 1];
    }

    public static function isWeekend(self $day): bool
    {
        return in_array($day, [self::Saturday, self::Sunday], true);
    }

    public function isWeekday(): bool
    {
        return !self::isWeekend($this);
    }
}

$today = Weekday::today();
echo "Today is {$today->name}";
echo $today->isWeekday() ? ' (工作日)' : ' (周末)';

枚举常量

php
<?php

declare(strict_types=1);

enum MediaType
{
    case Image;
    case Video;
    case Audio;
    case Document;

    final public const DEFAULT = self::Document;

    public function maxSizeKb(): int
    {
        return match ($this) {
            self::Image => 5120,
            self::Video => 102400,
            self::Audio => 25600,
            self::Document => 10240,
        };
    }
}

$default = MediaType::DEFAULT;
echo $default->name;  // "Document"

详细说明

枚举中的属性限制

php
<?php

declare(strict_types=1);

enum BrokenEnum
{
    case A;
    case B;

    // 以下都是非法的:
    // public string $value;          // Fatal error: Enums may not include properties
    // public static int $count = 0;  // Fatal error
    // private int $id;                // Fatal error

    // 但常量是允许的
    public const VERSION = '1.0';

    // 方法是允许的
    public function description(): string
    {
        return $this->name;
    }
}

重要限制

枚举不能包含属性(properties)。如果需要在 case 之间共享数据,只能通过方法返回值或构造函数参数(Backed Enum 的 from 方法)实现。

枚举构造函数

php
<?php

declare(strict_types=1);

enum Color
{
    case Red;
    case Green;
    case Blue;

    // 枚举可以有构造函数,但不能有参数
    // 用于初始化每个 case 的内部状态(如有 trait 需要的话)
    // 构造函数必须是 public 或 private,不能是 protected
}

实现接口

php
<?php

declare(strict_types=1);

interface HasLabel
{
    public function label(): string;
    public function description(): string;
}

enum PaymentStatus implements HasLabel
{
    case Pending;
    case Completed;
    case Failed;
    case Refunded;

    public function label(): string
    {
        return match ($this) {
            self::Pending => '待支付',
            self::Completed => '已完成',
            self::Failed => '失败',
            self::Refunded => '已退款',
        };
    }

    public function description(): string
    {
        return "支付状态: {$this->label()}";
    }
}

实战示例

场景一:HTTP 请求方法枚举

php
<?php

declare(strict_types=1);

enum RequestMethod
{
    case Get;
    case Post;
    case Put;
    case Patch;
    case Delete;
    case Options;
    case Head;

    public function isSafe(): bool
    {
        return in_array($this, [self::Get, self::Head, self::Options], true);
    }

    public function hasBody(): bool
    {
        return in_array($this, [self::Post, self::Put, self::Patch], true);
    }

    public function isIdempotent(): bool
    {
        return in_array($this, [self::Get, self::Put, self::Delete, self::Head, self::Options], true);
    }
}

场景二:访问控制级别

php
<?php

declare(strict_types=1);

enum Permission
{
    case Read;
    case Write;
    case Delete;
    case Admin;

    public function rank(): int
    {
        return match ($this) {
            self::Read => 1,
            self::Write => 2,
            self::Delete => 4,
            self::Admin => 8,
        };
    }

    public function includes(Permission $other): bool
    {
        return $this->rank() >= $other->rank();
    }
}

$admin = Permission::Admin;
$user = Permission::Read;

var_dump($admin->includes($user));   // true - Admin 包含 Read
var_dump($user->includes($admin));   // false - Read 不包含 Admin

注意事项

注意事项

  • 枚举不能包含属性(properties),只能包含 case、方法、常量和 trait
  • 枚举构造函数不能有参数(每个 case 无需传参)
  • 纯枚举没有 value 属性
  • 枚举的 name 属性是只读的,不能修改

小贴士

  • 使用 match 表达式处理枚举,确保穷尽所有 case
  • 纯枚举适用于不需要序列化值的场景(如状态标识)
  • 如果需要存储到数据库,使用 Backed Enum(带值枚举)

最佳实践

1. 为枚举添加描述性方法

php
<?php

declare(strict_types=1);

enum Color
{
    case Red;
    case Green;
    case Blue;

    public function hex(): string
    {
        return match ($this) {
            self::Red => '#FF0000',
            self::Green => '#00FF00',
            self::Blue => '#0000FF',
        };
    }
}

2. 保持 case 命名使用大驼峰

php
<?php

declare(strict_types=1);

// 推荐 - PascalCase
enum OrderStatus
{
    case PendingReview;
    case Approved;
    case Rejected;
}

参考链接