Skip to content

ReflectionType / NamedType / UnionType / IntersectionType

概述

PHP 的类型系统在 PHP 7.0 引入返回类型声明后不断增强。反射 API 也随之扩展,提供了对类型声明的完整反射支持。ReflectionType 是所有类型反射的基类,ReflectionNamedType 表示单一类型,ReflectionUnionType(PHP 8.0+)表示联合类型,ReflectionIntersectionType(PHP 8.1+)表示交集类型。

版本演进

  • PHP 7.0: 返回类型声明 + ReflectionType
  • PHP 8.0: 联合类型(int|string)+ ReflectionUnionType
  • PHP 8.1: 交集类型(A&B)+ ReflectionIntersectionType

基础概念

ReflectionType

所有类型反射的接口/基类,提供 allowsNull()__toString() 方法。

ReflectionNamedType

表示单一命名类型,是最常用的类型反射。

ReflectionUnionType / ReflectionIntersectionType

分别表示联合类型和交集类型。

语法与代码

基本类型反射

php
<?php
declare(strict_types=1);

function example(
    int $a,
    string $b,
    ?float $c,
    array $d,
    callable $e
): void {}

$refFunc = new \ReflectionFunction('example');

foreach ($refFunc->getParameters() as $param) {
    $type = $param->getType();
    $typeName = $type?->getName() ?? 'none';
    $allowsNull = $type?->allowsNull() ?? true;
    $isBuiltin = $type instanceof \ReflectionNamedType ? ($type->isBuiltin() ? 'yes' : 'no') : 'n/a';
    echo "\${$param->getName()}: {$typeName}" . ($allowsNull ? '|null' : '') . " (builtin: {$isBuiltin})\n";
}

联合类型反射(PHP 8.0+)

php
<?php
declare(strict_types=1);

function processInput(int|string $input): bool
{
    return true;
}

$refFunc = new \ReflectionFunction('processInput');
$param = $refFunc->getParameters()[0];
$type = $param->getType();

if ($type instanceof \ReflectionUnionType) {
    echo "联合类型成员:\n";
    foreach ($type->getTypes() as $memberType) {
        echo "  - " . $memberType->getName() . "\n";
    }
}

交集类型反射(PHP 8.1+)

php
<?php
declare(strict_types=1);

function iterate(Countable&\Iterator $collection): void {}

$refFunc = new \ReflectionFunction('iterate');
$param = $refFunc->getParameters()[0];
$type = $param->getType();

if ($type instanceof \ReflectionIntersectionType) {
    echo "交集类型成员:\n";
    foreach ($type->getTypes() as $memberType) {
        echo "  - " . $memberType->getName() . "\n";
    }
}

类型检查辅助函数

php
<?php
declare(strict_types=1);

class TypeInspector
{
    public function inspectParameter(\ReflectionParameter $param): string
    {
        $type = $param->getType();
        if ($type === null) return 'no type';

        if ($type instanceof \ReflectionNamedType) {
            $name = $type->getName();
            return $type->allowsNull() ? "{$name}|null" : $name;
        }

        if ($type instanceof \ReflectionUnionType) {
            return implode('|', array_map(
                fn(\ReflectionNamedType $t) => $t->getName(),
                $type->getTypes()
            ));
        }

        if ($type instanceof \ReflectionIntersectionType) {
            return implode('&', array_map(
                fn(\ReflectionNamedType $t) => $t->getName(),
                $type->getTypes()
            ));
        }

        return (string) $type;
    }
}

详细说明

类型反射类层次

ReflectionType (interface)
├── ReflectionNamedType        — 单一类型 (int, string, MyClass)
├── ReflectionUnionType       — 联合类型 (int|string)
├── ReflectionIntersectionType — 交集类型 (A&B)

allowsNull() 的行为

类型声明allowsNull()
intfalse
?inttrue
`intstring`
`intstring

实战示例

实战:类型安全的参数验证器

php
<?php
declare(strict_types=1);

class TypeValidator
{
    public function validateParameter(mixed $value, \ReflectionParameter $param): bool
    {
        $type = $param->getType();
        if ($type === null) return true;

        if ($type instanceof \ReflectionNamedType) {
            return $this->checkNamedType($value, $type);
        }

        if ($type instanceof \ReflectionUnionType) {
            return $this->checkUnionType($value, $type);
        }

        return true;
    }

    private function checkNamedType(mixed $value, \ReflectionNamedType $type): bool
    {
        if ($type->allowsNull() && $value === null) return true;
        return match ($type->getName()) {
            'int' => is_int($value),
            'float' => is_float($value),
            'string' => is_string($value),
            'bool' => is_bool($value),
            'array' => is_array($value),
            default => $value instanceof $type->getName(),
        };
    }

    private function checkUnionType(mixed $value, \ReflectionUnionType $type): bool
    {
        foreach ($type->getTypes() as $memberType) {
            if ($this->checkNamedType($value, $memberType)) return true;
        }
        return false;
    }
}

注意事项

void 类型

void 类型在 ReflectionNamedTypegetName() 返回 'void',且 allowsNull() 返回 false

最佳实践

  1. 使用 instanceof 检查类型:通过 instanceof 区分不同的类型反射类。
  2. 处理 null 类型:注意 allowsNull() 在联合类型中的行为。
  3. 缓存类型信息:类型反射结果通常可以缓存。

类型反射高级应用

获取所有类型信息的完整工具

php
<?php
declare(strict_types=1);

class TypeInspector
{
    public function describe(\ReflectionType $type): array
    {
        if ($type instanceof \ReflectionNamedType) {
            return $this->describeNamedType($type);
        }

        if ($type instanceof \ReflectionUnionType) {
            return $this->describeUnionType($type);
        }

        if ($type instanceof \ReflectionIntersectionType) {
            return $this->describeIntersectionType($type);
        }

        return ['kind' => 'unknown', 'toString' => (string) $type];
    }

    private function describeNamedType(\ReflectionNamedType $type): array
    {
        return [
            'kind' => 'named',
            'name' => $type->getName(),
            'allowsNull' => $type->allowsNull(),
            'isBuiltin' => $type->isBuiltin(),
        ];
    }

    private function describeUnionType(\ReflectionUnionType $type): array
    {
        return [
            'kind' => 'union',
            'allowsNull' => $type->allowsNull(),
            'types' => array_map(
                fn(\ReflectionNamedType $t) => $this->describeNamedType($t),
                $type->getTypes()
            ),
        ];
    }

    private function describeIntersectionType(\ReflectionIntersectionType $type): array
    {
        return [
            'kind' => 'intersection',
            'types' => array_map(
                fn(\ReflectionNamedType $t) => $this->describeNamedType($t),
                $type->getTypes()
            ),
        ];
    }
}

处理可空类型的各种形式

PHP 中有多种表示可空类型的方式,类型反射需要正确处理:

php
<?php
declare(strict_types=1);

// 形式 1: ?int (可空类型修饰符)
function nullableModifier(?int $value): void {}

// 形式 2: int|null (联合类型包含 null)
function nullableUnion(int|null $value): void {}

// 形式 3: mixed (隐式允许 null)
function mixedType(mixed $value): void {}

// 分析这三种形式的区别
$functions = ['nullableModifier', 'nullableUnion', 'mixedType'];

foreach ($functions as $funcName) {
    $ref = new \ReflectionFunction($funcName);
    $param = $ref->getParameters()[0];
    $type = $param->getType();

    echo "函数: {$funcName}\n";
    echo "  类型声明: " . ($type ? (string) $type : 'none') . "\n";
    echo "  allowsNull: " . ($type?->allowsNull() ? 'true' : 'false') . "\n";
    echo "  类型类: " . ($type !== null ? $type::class : 'none') . "\n";
    echo "\n";
}

三种可空形式的区别

虽然 ?intint|null 在语义上等价,但反射结果不同:

  • ?intReflectionNamedTypegetName() 返回 'int'allowsNull() 返回 true
  • int|nullReflectionUnionType,包含 intnull 两个 ReflectionNamedType

类型反射在 DI 容器中的应用

php
<?php
declare(strict_types=1);

interface LoggerInterface {}

class FileLogger implements LoggerInterface {}

class Application
{
    public function __construct(
        private readonly LoggerInterface $logger,
        private readonly string $appName = 'MyApp',
        private readonly int $maxRetries = 3,
    ) {}
}

// 通过反射分析构造函数参数类型
$refClass = new \ReflectionClass(Application::class);
$constructor = $refClass->getConstructor();

foreach ($constructor->getParameters() as $param) {
    $type = $param->getType();

    echo "参数: \${$param->getName()}\n";

    if ($type instanceof \ReflectionNamedType) {
        echo "  类型: " . $type->getName() . "\n";
        echo "  内置类型: " . ($type->isBuiltin() ? 'yes' : 'no') . "\n";

        if (!$type->isBuiltin()) {
            echo "  需要从容器解析依赖: " . $type->getName() . "\n";
        }
    }

    if ($param->isDefaultValueAvailable()) {
        echo "  默认值: " . var_export($param->getDefaultValue(), true) . "\n";
    }

    echo "\n";
}

静态返回类型反射

php
<?php
declare(strict_types=1);

class Builder
{
    public static function create(): static
    {
        return new static();
    }

    public function self(): self
    {
        return $this;
    }
}

$refClass = new \ReflectionClass(Builder::class);

foreach ($refClass->getMethods() as $method) {
    $returnType = $method->getReturnType();
    if ($returnType !== null) {
        echo "{$method->getName()} 返回类型: " . $returnType->getName() . "\n";
    }
}

常见误区与 FAQ

ReflectionNamedType::isBuiltin() 返回 false 的是类名吗?

不完全是。返回 false 表示它不是 PHP 内置类型,但它可能是类名、接口名或 static/self/parent 等伪类型。

mixed 类型的反射结果是什么?

mixed 类型返回 ReflectionNamedTypegetName() 返回 'mixed'isBuiltin() 返回 trueallowsNull() 返回 true

static 返回类型如何反射?

static 返回 ReflectionNamedTypegetName() 返回 'static'isBuiltin() 返回 false。这意味着容器不能直接实例化 static 类型。

参考链接