Skip to content

ReflectionEnum / ReflectionEnumCase / ReflectionFiber

概述

PHP 8.1 引入了枚举(Enum)和纤程(Fiber),反射 API 也随之扩展。ReflectionEnum 用于检查枚举类型的结构,ReflectionEnumCase 用于检查枚举的各个 case,ReflectionFiber 用于检查纤程的状态。本章将详细讲解这三个反射类的用法。

PHP 版本

这些反射类均从 PHP 8.1 开始可用。

基础概念

ReflectionEnum

检查枚举类型(Enum)的信息,包括枚举类型(纯枚举/回退枚举)、case 列表、枚举值的类型等。

ReflectionEnumCase

检查枚举的各个 case,包括名称、值、是否 backed、所属枚举类型等。

ReflectionFiber

检查纤程的状态信息,包括执行状态、调用栈等。

语法与代码

ReflectionEnum 基本用法

php
<?php
declare(strict_types=1);

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Pending = 'pending';
}

$refEnum = new \ReflectionEnum(Status::class);

echo "枚举名: " . $refEnum->getName() . "\n";
echo "是否为枚举: " . ($refEnum->isEnum() ? 'yes' : 'no') . "\n";
echo "是否为 backed: " . ($refEnum->isBacked() ? 'yes' : 'no') . "\n";

if ($refEnum->isBacked()) {
    echo "枚举值类型: " . $refEnum->getBackingType()?->getName() . "\n";
}

echo "\n所有 case:\n";
foreach ($refEnum->getCases() as $case) {
    echo "  " . $case->getName();
    if ($case->isBacked()) {
        echo " = " . var_export($case->getValue(), true);
    }
    echo "\n";
}

ReflectionEnumCase 基本用法

php
<?php
declare(strict_types=1);

$case = new \ReflectionEnumCase(Status::class, 'Active');

echo "Case 名称: " . $case->getName() . "\n";
echo "Case 值: " . $case->getValue()->value . "\n";
echo "所属枚举: " . $case->getEnum()->getName() . "\n";
echo "是否 backed: " . ($case->isBacked() ? 'yes' : 'no') . "\n";

// 获取枚举实例
$active = $case->getValue();
echo $active instanceof Status; // true

纯枚举 vs Backed 枚举

php
<?php
declare(strict_types=1);

// 纯枚举
enum Color
{
    case Red;
    case Green;
    case Blue;
}

// Backed 枚举
enum Role: int
{
    case Admin = 1;
    case Editor = 2;
    case Viewer = 3;
}

function inspectEnum(string $enumClass): void
{
    $ref = new \ReflectionEnum($enumClass);

    echo "枚举: " . $ref->getShortName() . "\n";
    echo "Backed: " . ($ref->isBacked() ? 'yes' : 'no') . "\n";

    if ($ref->isBacked()) {
        echo "值类型: " . $ref->getBackingType()->getName() . "\n";
    }

    echo "Cases:\n";
    foreach ($ref->getCases() as $case) {
        echo "  " . $case->getName();
        if ($case->isBacked()) {
            echo " = " . var_export($case->getValue()->value, true);
        }
        echo "\n";
    }
    echo "\n";
}

inspectEnum(Color::class);
inspectEnum(Role::class);

枚举上的注解反射

php
<?php
declare(strict_types=1);

use Attribute;

#[Attribute(Attribute::TARGET_ENUM_CASE)]
class Description
{
    public function __construct(public readonly string $text) {}
}

#[Attribute(Attribute::TARGET_CLASS)]
class Label
{
    public function __construct(public readonly string $name) {}
}

#[Label(name: 'HTTP 方法')]
enum HttpMethod: string
{
    #[Description(text: '获取资源')]
    case Get = 'GET';

    #[Description(text: '创建资源')]
    case Post = 'POST';

    #[Description(text: '更新资源')]
    case Put = 'PUT';
}

// 读取枚举类上的注解
$refEnum = new \ReflectionEnum(HttpMethod::class);
$labelAttrs = $refEnum->getAttributes(Label::class);
foreach ($labelAttrs as $attr) {
    $label = $attr->newInstance();
    echo "枚举标签: {$label->name}\n";
}

// 读取枚举 case 上的注解
foreach ($refEnum->getCases() as $case) {
    $descAttrs = $case->getAttributes(Description::class);
    foreach ($descAttrs as $descAttr) {
        $desc = $descAttr->newInstance();
        echo "{$case->getName()}: {$desc->text}\n";
    }
}

ReflectionFiber 基本用法

php
<?php
declare(strict_types=1);

$fiber = new \Fiber(function (): void {
    Fiber::suspend('step 1');
    Fiber::suspend('step 2');
    return 'done';
});

$fiber->start();

$refFiber = new \ReflectionFiber($fiber);

echo "状态: " . $refFiber->status() . "\n"; // SUSPENDED (3)

$fiber->resume();
$refFiber = new \ReflectionFiber($fiber);
echo "状态: " . $refFiber->status() . "\n"; // SUSPENDED (3)

$fiber->resume();
$refFiber = new \ReflectionFiber($fiber);
echo "状态: " . $refFiber->status() . "\n"; // FINISHED (4)

详细说明

ReflectionEnum 常用方法

方法说明
getName()枚举完整类名
isEnum()是否为枚举
isBacked()是否为 backed 枚举
getBackingType()获取值类型(仅 backed 枚举)
getCases()获取所有 case
hasCase(string $name)是否有指定 case
getCase(string $name)获取指定 case

ReflectionEnumCase 常用方法

方法说明
getName()case 名称
getValue()枚举实例
getEnum()所属枚举类型
isBacked()是否有值
getAttributes()获取注解

ReflectionFiber 方法

方法说明
status()纤程状态
getFiber()获取 Fiber 实例

实战示例

实战:枚举信息导出器

php
<?php
declare(strict_types=1);

class EnumExporter
{
    public function export(string $enumClass): array
    {
        $ref = new \ReflectionEnum($enumClass);
        $result = [
            'name' => $ref->getShortName(),
            'backed' => $ref->isBacked(),
            'type' => $ref->isBacked() ? $ref->getBackingType()->getName() : null,
            'cases' => [],
        ];

        foreach ($ref->getCases() as $case) {
            $caseInfo = [
                'name' => $case->getName(),
                'value' => $case->isBacked() ? $case->getValue()->value : null,
            ];

            // 读取 case 上的注解
            $attrs = [];
            foreach ($case->getAttributes() as $attr) {
                $attrs[$attr->getName()] = $attr->getArguments();
            }
            $caseInfo['attributes'] = $attrs;

            $result['cases'][] = $caseInfo;
        }

        return $result;
    }
}

$exporter = new EnumExporter();
print_r($exporter->export(HttpMethod::class));

注意事项

ReflectionEnum 只能用于枚举

对非枚举类使用 ReflectionEnum 会抛出 ReflectionException

ReflectionFiber 的局限性

ReflectionFiber 提供的信息有限,主要用于调试。

最佳实践

  1. 利用枚举反射构建表单:根据枚举的 case 和注解自动生成表单选项。
  2. 缓存枚举反射结果:枚举结构在运行时不变,可以安全缓存。
  3. 配合注解使用:在枚举 case 上使用注解添加元数据。
php
<?php
declare(strict_types=1);

function getEnumOptions(string $enumClass): array
{
    $ref = new \ReflectionEnum($enumClass);
    return array_map(
        fn(\ReflectionEnumCase $case) => [
            'name' => $case->getName(),
            'value' => $case->getValue(),
        ],
        $ref->getCases()
    );
}

ReflectionEnum 详细用法

枚举类型完整分析

php
<?php
declare(strict_types=1);

enum Status: string
{
    case Active = 'active';
    case Inactive = 'inactive';
    case Pending = 'pending';
    case Archived = 'archived';
}

enum Role
{
    case Admin;
    case Editor;
    case Viewer;
}

function analyzeEnum(string $enumClass): array
{
    $refEnum = new \ReflectionEnum($enumClass);

    return [
        'name' => $refEnum->getName(),
        'isBacked' => $refEnum->isBacked(),
        'backingType' => $refEnum->getBackingType()?->getName(),
        'cases' => array_map(
            fn(\ReflectionEnumUnitCase|\ReflectionEnumBackedCase $case) => [
                'name' => $case->getName(),
                'value' => $case instanceof \ReflectionEnumBackedCase
                    ? $case->getEnum()->getValue() : null,
            ],
            $refEnum->getCases()
        ),
        'hasCases' => $refEnum->hasCase('Active'),
        'caseCount' => count($refEnum->getCases()),
    ];
}

print_r(analyzeEnum(Status::class));

枚举 case 的反射

php
<?php
declare(strict_types=1);

enum HttpMethod: string
{
    case Get = 'GET';
    case Post = 'POST';
    case Put = 'PUT';
    case Delete = 'DELETE';
    case Patch = 'PATCH';
}

function enumCaseDetails(\ReflectionEnumBackedCase $case): array
{
    return [
        'name' => $case->getName(),
        'value' => $case->getValue(),
        'enum' => $case->getEnum()->getName(),
        'declaringClass' => $case->getDeclaringClass()->getName(),
    ];
}

$refEnum = new \ReflectionEnum(HttpMethod::class);

foreach ($refEnum->getCases() as $case) {
    if ($case instanceof \ReflectionEnumBackedCase) {
        print_r(enumCaseDetails($case));
    }
}

枚举方法反射

php
<?php
declare(strict_types=1);

enum Color: string
{
    case Red = '#FF0000';
    case Green = '#00FF00';
    case Blue = '#0000FF';

    public function isWarm(): bool
    {
        return in_array($this, [self::Red, self::Green], true);
    }

    public function toRgb(): array
    {
        return sscanf($this->value, '#%02x%02x%02x');
    }

    public static function fromName(string $name): self
    {
        return self::from($name);
    }
}

$refEnum = new \ReflectionEnum(Color::class);

echo "枚举方法:\n";
foreach ($refEnum->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
    if (!$method->isStatic()) {
        $returnType = $method->getReturnType()?->getName() ?? 'void';
        echo "  {$method->getName()}(): {$returnType}\n";
    }
}

ReflectionFiber 详解

获取 Fiber 的状态信息

php
<?php
declare(strict_types=1);

$fiber = new \Fiber(function (): void {
    echo "Fiber 开始\n";
    \Fiber::suspend('first pause');
    echo "Fiber 恢复\n";
    \Fiber::suspend('second pause');
    echo "Fiber 结束\n");
});

$fiber->start();

// 通过反射获取 Fiber 状态
$refFiber = new \ReflectionFiber($fiber);

echo "Fiber 状态: ";
echo match ($refFiber->getFiberStatus()) {
    \Fiber::STATUS_INIT => 'init (未启动)',
    \Fiber::STATUS_SUSPENDED => 'suspended (挂起)',
    \Fiber::STATUS_RUNNING => 'running (运行中)',
    \Fiber::STATUS_FINISHED => 'finished (已完成)',
    \Fiber::STATUS_DEAD => 'dead (异常终止)',
    default => 'unknown',
} . "\n";

// 获取 Fiber 函数信息
$executingFunction = $refFiber->getExecutingFunction();
echo "执行函数: " . $executingFunction->getName() . "\n";

$fiber->resume();
$refFiber = new \ReflectionFiber($fiber);
echo "恢复后状态: ";
echo match ($refFiber->getFiberStatus()) {
    \Fiber::STATUS_SUSPENDED => 'suspended',
    \Fiber::STATUS_FINISHED => 'finished',
    default => 'unknown',
} . "\n";

常见误区与 FAQ

ReflectionEnum 只适用于 enum 吗?

是的。ReflectionEnum 只能反射枚举类型。对普通类使用会抛出异常。

如何区分 backed enum 和 unit enum?

使用 ReflectionEnum::isBacked() 方法。Backed enum 有 getBackingType() 返回的类型信息。

ReflectionFiber 的 getFiberStatus 与 Fiber 方法的关系?

ReflectionFiber::getFiberStatus() 返回 Fiber 的当前状态常量,与 Fiber 类的状态检查方法(isRunning()isSuspended() 等)返回布尔值不同。反射方式返回精确的状态码。

参考链接