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() |
|---|---|
int | false |
?int | true |
| `int | string` |
| `int | string |
实战示例
实战:类型安全的参数验证器
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 类型在 ReflectionNamedType 中 getName() 返回 'void',且 allowsNull() 返回 false。
最佳实践
- 使用 instanceof 检查类型:通过
instanceof区分不同的类型反射类。 - 处理 null 类型:注意
allowsNull()在联合类型中的行为。 - 缓存类型信息:类型反射结果通常可以缓存。
类型反射高级应用
获取所有类型信息的完整工具
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";
}三种可空形式的区别
虽然 ?int 和 int|null 在语义上等价,但反射结果不同:
?int→ReflectionNamedType,getName()返回'int',allowsNull()返回trueint|null→ReflectionUnionType,包含int和null两个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 类型返回 ReflectionNamedType,getName() 返回 'mixed',isBuiltin() 返回 true,allowsNull() 返回 true。
static 返回类型如何反射?
static 返回 ReflectionNamedType,getName() 返回 'static',isBuiltin() 返回 false。这意味着容器不能直接实例化 static 类型。