Union Types 在反射 API 中的获取方式

解读

国内一线/二线互联网公司的 PHP 面试,常把「类型系统」作为区分初中高级的重要分水岭。Union Types(联合类型)在 PHP 8.0 正式落地后,面试官喜欢追问「如何拿到运行时信息」。表面看是考反射,实质是验证候选人对「类型收窄、静态分析、IDE 友好」的理解深度。答不上来会被直接判定为「只用 PHP 写脚本,没写过企业级代码」。

知识点

  1. PHP 8.0+ 的 ReflectionNamedType 与 ReflectionUnionType 继承关系
  2. ReflectionParameter::getType() / ReflectionFunction::getReturnType() 返回值类型
  3. ReflectionUnionType::getTypes() 迭代子类型
  4. 区分「nullable」与「union 包含 null」的两种写法:?T 与 T|null
  5. 与 static analysis 工具(phpstan/psalm)的联动:反射结果直接影响 level 6+ 的推导
  6. 性能注意:反射在 OPcache 预热阶段做一次即可,切勿在请求循环里反复 new ReflectionFunction

答案

function demo(int|string $id, float|null $rate): array|bool { … }

$refl = new ReflectionFunction('demo');

// 获取参数联合类型
$param = $refl->getParameters()[0];
$type  = $param->getType();        // ReflectionUnionType
foreach ($type->getTypes() as $t) {
    echo $t->getName();            // 依次输出 int、string
}

// 获取返回联合类型
$returnType = $refl->getReturnType();
if ($returnType instanceof ReflectionUnionType) {
    $names = array_map(fn($t) => $t->getName(), $returnType->getTypes());
    // $names = ['array', 'bool']
}

// 判断可否为空
$allowsNull = $returnType->allowsNull(); // false,因为 array|bool 没写 null

一句话总结:先拿 ReflectionParameter/ReflectionFunctionAbstract 的 getType(),判断 instanceof ReflectionUnionType,再用 getTypes() 遍历即可。

拓展思考

  1. 如果联合类型里包含 class 名,如何进一步拿到 ReflectionClass?
    答:t>getName()得到类名后newReflectionClass(t->getName() 得到类名后 new ReflectionClass(t->getName()),但要 catch 自动加载异常。
  2. PHP 8.2 的 DNF(Disjunctive Normal Form)类型如 (A&B)|C 时,getTypes() 只会返回顶层 ReflectionIntersectionType 与 ReflectionNamedType,需要递归解析。
  3. 在 Laravel 容器里,Controller 依赖注入的 union 参数可通过 Illuminate\Container\Util::getParameterClassName() 统一收口,底层同样用 ReflectionUnionType 判断,源码是值得背诵的面试加分项。