PHP 8.1+ 原生支持 enum,所谓“模拟”仅存在于两类场景:一是项目仍运行在 PHP < 8.1 且需兼容旧代码;二是需自定义序列化或 Laravel 模型类型转换。

PHP 8.1+ 已原生支持 enum,所谓“模拟”只在两类场景下真实存在:一是项目仍运行在 PHP class 常量或魔术方法不是真模拟,容易漏掉类型约束、序列化、反射等关键行为——真正可用的模拟必须逼近原生枚举的契约。
PHP
不能只靠 class + const。原生 enum 的核心是「单例实例 + 类型可验 + 值可取」,模拟必须覆盖这三点:
- 每个 case 必须是该类的唯一实例(用
private static self $instance+__clone+__wakeup封禁克隆和反序列化) - 提供
from(string|int $value): static和tryFrom(string|int $value): ?static,行为要和原生一致(失败抛ValueError或返回null) - 暴露
->value(非->name)作为底层标量,且只读;__get('value')是最简实现方式 - 数据库读写时,必须显式调用
MyEnum::from($dbValue)而非直接赋值,否则类型检查失效
示例关键片段:
final class Status
{
private const PENDING = 'pending';
private const APPROVED = 'approved';
private static self $pending;
private static self $approved;
private function __construct(private string $value) {}
public static function PENDING(): self
{
return self::$pending ??= new self(self::PENDING);
}
public static function from(string $value): self
{
return match ($value) {
self::PENDING => self::PENDING(),
self::APPROVED => self::APPROVED(),
default => throw new ValueError("Invalid status value: {$value}"),
};
}
public function __get(string $name): mixed
{
if ($name === 'value') {
return $this->value;
}
throw new Error("Cannot access property {$name}");
}
}
为什么不能用 array_key_exists($input, MyEnum::cases()) 校验输入
因为 MyEnum::cases() 是 PHP 8.2+ 才有的静态方法,PHP cases(),返回的是对象数组,而 array_key_exists() 查的是键名——你传的是字符串 'pending',它却在找键为 0、1 的数组项,必然 false。
立即学习“PHP免费学习笔记(深入)”;
- 正确做法是提取所有合法值:用
array_column(MyEnum::cases(), 'value')得到['pending', 'approved'],再用in_array($input, $values, true) - 更轻量:直接在
from()方法里做校验,避免重复逻辑 - 注意
match表达式不接受模拟枚举实例——PHP 解析器不识别,只能用if/switch+===比较对象引用
从 class 模拟迁移到 PHP 8.1+ 原生 enum 的三步踩坑点
不是改个语法就完事。以下操作缺一不可,否则运行时报 TypeError 或静默错乱:
- 所有函数参数、属性声明、返回类型提示,必须从
string或MyStatus(旧类名)改为Status(新 enum 名),包括 Doctrine/PHPStan/Laravel Validation Rule 中的类型声明 - 数据库字段若存的是字符串(如 MySQL
VARCHAR),读取后必须用Status::from($row['status'])转换;写入时用$status->value,不能直接(string) $status(会触发__toString未定义错误) - JSON 序列化默认只输出
->value,但若旧前端依赖{ "status": "pending" },无需改动;若曾手动重载JsonSerializable输出对象结构,则必须删掉——原生 enum 不允许自定义序列化
最容易被忽略的是 Laravel 的 casts:模型里写 protected $casts = ['status' => Status::class] 会失败,必须改成 protected $casts = ['status' => StatusCast::class] 并自定义 cast 类,内部调用 from() 和 ->value。



















