ReflectionFunction / ReflectionParameter
概述
ReflectionFunction 用于检查独立函数和闭包的信息,ReflectionParameter 用于检查函数/方法的参数信息。两者配合使用,可以在运行时获取函数的完整签名,包括参数名称、类型、默认值、是否可变参数等。对于闭包,还可以通过反射获取其绑定的作用域和变量。
扩展关系
ReflectionFunction 和 ReflectionMethod 都继承自 ReflectionFunctionAbstract,共享大部分方法。
基础概念
ReflectionFunction
检查函数的名称、参数、返回类型、文件位置等。
ReflectionParameter
检查参数的名称、类型、是否有默认值、是否通过引用传递等。
闭包反射
闭包的 ReflectionFunction 可以检查其绑定的 $this 和 use 变量。
语法与代码
检查函数信息
php
<?php
declare(strict_types=1);
function sendEmail(
string $to,
string $subject,
string $body,
array $headers = [],
bool $html = true
): bool {
return true;
}
$refFunc = new \ReflectionFunction('sendEmail');
echo "函数名: " . $refFunc->getName() . "\n";
echo "文件: " . $refFunc->getFileName() . "\n";
echo "起始行: " . $refFunc->getStartLine() . "\n";
echo "返回类型: " . $refFunc->getReturnType()?->getName() . "\n";
echo "参数数量: " . $refFunc->getNumberOfParameters() . "\n";
echo "必需参数: " . $refFunc->getNumberOfRequiredParameters() . "\n";检查参数信息
php
<?php
declare(strict_types=1);
$refFunc = new \ReflectionFunction('sendEmail');
foreach ($refFunc->getParameters() as $param) {
$type = $param->getType();
if ($type instanceof \ReflectionNamedType) {
echo "\${$param->getName()}: {$type->getName()}"
. ($type->allowsNull() ? '|null' : '') . "\n";
}
echo " 通过引用: " . ($param->isPassedByReference() ? 'yes' : 'no') . "\n";
echo " 可变参数: " . ($param->isVariadic() ? 'yes' : 'no') . "\n";
}闭包反射
php
<?php
declare(strict_types=1);
$greeting = 'Hello';
$closure = function (string $name) use ($greeting): string {
return "{$greeting}, {$name}!";
};
$refClosure = new \ReflectionFunction($closure);
echo "是闭包: " . ($refClosure->isClosure() ? 'yes' : 'no') . "\n";
$staticVars = $refClosure->getStaticVariables();
print_r($staticVars);
// Array ( [greeting] => Hello )调用函数
php
<?php
declare(strict_types=1);
function add(int $a, int $b): int
{
return $a + $b;
}
$refFunc = new \ReflectionFunction('add');
echo $refFunc->invoke(10, 20); // 30
echo $refFunc->invokeArgs([5, 15]); // 20详细说明
ReflectionFunction vs ReflectionMethod
| 特性 | ReflectionFunction | ReflectionMethod |
|---|---|---|
| 目标 | 独立函数、闭包 | 类方法 |
isClosure() | 支持 | 不支持 |
getClosureScopeClass() | 支持 | 不支持 |
getStaticVariables() | 支持(仅闭包) | 不支持 |
ReflectionParameter 常用方法
| 方法 | 说明 |
|---|---|
getName() | 参数名 |
getPosition() | 参数位置 |
getType() | 类型 |
isDefaultValueAvailable() | 是否有默认值 |
getDefaultValue() | 默认值 |
isPassedByReference() | 是否引用传递 |
isVariadic() | 是否可变参数 |
allowsNull() | 是否允许 null |
实战示例
实战:函数签名生成器
php
<?php
declare(strict_types=1);
class SignatureGenerator
{
public function generate(string|callable $function): string
{
if (is_string($function)) {
$ref = new \ReflectionFunction($function);
} elseif ($function instanceof \Closure) {
$ref = new \ReflectionFunction($function);
} else {
$ref = new \ReflectionMethod(...$function);
}
$params = array_map(
fn(\ReflectionParameter $p): string => $this->formatParam($p),
$ref->getParameters()
);
$returnType = $ref->getReturnType();
$returnStr = $returnType ? ': ' . $returnType->getName() : '';
return "function {$ref->getName()}(" . implode(', ', $params) . "){$returnStr}";
}
private function formatParam(\ReflectionParameter $param): string
{
$parts = [];
$type = $param->getType();
if ($type instanceof \ReflectionNamedType) {
$parts[] = $type->getName();
}
if ($param->isPassedByReference()) {
$parts[] = '&';
}
$parts[] = '$' . $param->getName();
if ($param->isDefaultValueAvailable()) {
$parts[] = '= ' . var_export($param->getDefaultValue(), true);
}
return implode(' ', $parts);
}
}注意事项
可变参数的反射
php
<?php
declare(strict_types=1);
function sum(int ...$numbers): int
{
return array_sum($numbers);
}
$refFunc = new \ReflectionFunction('sum');
$lastParam = $refFunc->getParameters()[0];
echo $lastParam->isVariadic(); // true最佳实践
- 使用函数签名验证:通过反射验证回调函数的签名是否符合预期。
- 缓存反射结果。
ReflectionFunction 高级用法
获取函数的完整文档信息
php
<?php
declare(strict_types=1);
function calculateTotal(
int $basePrice,
float $taxRate,
array $discounts = [],
bool $includeShipping = true,
): float {
return 0.0;
}
function dumpFunctionDetails(string $functionName): array
{
$ref = new \ReflectionFunction($functionName);
return [
'name' => $ref->getName(),
'fileName' => $ref->getFileName(),
'startLine' => $ref->getStartLine(),
'endLine' => $ref->getEndLine(),
'numberOfParameters' => $ref->getNumberOfParameters(),
'requiredParameters' => $ref->getNumberOfRequiredParameters(),
'returnType' => $ref->getReturnType()?->getName(),
'isClosure' => $ref->isClosure(),
'isDeprecated' => $ref->isDeprecated(),
'isVariadic' => $ref->isVariadic(),
'inNamespace' => $ref->inNamespace(),
'namespaceName' => $ref->getNamespaceName(),
'parameters' => array_map(
fn(\ReflectionParameter $p) => [
'name' => $p->getName(),
'position' => $p->getPosition(),
'type' => $p->getType()?->getName(),
'allowsNull' => $p->allowsNull(),
'hasDefault' => $p->isDefaultValueAvailable(),
'byReference' => $p->isPassedByReference(),
'variadic' => $p->isVariadic(),
],
$ref->getParameters()
),
];
}
print_r(dumpFunctionDetails('calculateTotal'));闭包的静态变量分析
php
<?php
declare(strict_types=1);
$counter = 0;
$multiplier = 2;
$closure = function (int $value) use (&$counter, $multiplier): int {
$counter++;
return $value * $multiplier;
};
$refClosure = new \ReflectionFunction($closure);
echo "是闭包: " . ($refClosure->isClosure() ? 'yes' : 'no') . "\n";
echo "闭包作用域类: " . ($refClosure->getClosureScopeClass()?->getName() ?? 'none') . "\n";
echo "静态变量:\n";
print_r($refClosure->getStaticVariables());
// 调用闭包
echo "结果: " . $refClosure->invoke(5) . "\n";
echo "结果: " . $refClosure->invoke(10) . "\n";
echo "计数器: {$counter}\n";闭包的绑定分析
php
<?php
declare(strict_types=1);
class App
{
private string $prefix = '[APP] ';
public function getLogger(): \Closure
{
return function (string $message): string {
return $this->prefix . $message;
};
}
}
$app = new App();
$logger = $app->getLogger();
$refClosure = new \ReflectionFunction($logger);
// 获取绑定的 $this
$scopeClass = $refClosure->getClosureScopeClass();
if ($scopeClass !== null) {
echo "绑定到类: " . $scopeClass->getName() . "\n";
}
// 获取绑定的对象
if (method_exists($refClosure, 'getClosureThis')) {
$boundObj = $refClosure->getClosureThis();
if ($boundObj !== null) {
echo "绑定到实例: " . $boundObj::class . "\n";
}
}
echo $logger('Hello'); // [APP] Hello函数调用包装器
php
<?php
declare(strict_types=1);
class FunctionInvoker
{
public function invokeSafely(string $functionName, array $args = []): mixed
{
if (!function_exists($functionName)) {
throw new \BadFunctionCallException("函数不存在: {$functionName}");
}
$refFunc = new \ReflectionFunction($functionName);
if (count($args) < $refFunc->getNumberOfRequiredParameters()) {
throw new \ArgumentCountError(
"函数 {$functionName} 至少需要 {$refFunc->getNumberOfRequiredParameters()} 个参数"
);
}
return $refFunc->invokeArgs($args);
}
public function getArgumentsHint(string $functionName): string
{
$refFunc = new \ReflectionFunction($functionName);
$params = [];
foreach ($refFunc->getParameters() as $param) {
$type = $param->getType()?->getName() ?? 'mixed';
$name = $param->getName();
$default = $param->isDefaultValueAvailable()
? ' = ' . var_export($param->getDefaultValue(), true)
: '';
$params[] = "{$type} \${$name}{$default}";
}
return $functionName . '(' . implode(', ', $params) . ')';
}
}
$invoker = new FunctionInvoker();
echo $invoker->getArgumentsHint('array_map') . "\n";
echo $invoker->invokeSafely('strlen', ['hello']) . "\n";常见误区与 FAQ
ReflectionFunction 可以反射内置函数吗?
可以,但某些信息(如文件名、行号)不可用。isInternal() 返回 true。
如何获取闭包绑定的 use 变量?
使用 getStaticVariables() 方法。注意,这会返回 use 语句中捕获的变量,包括通过引用捕获的变量的当前值。
闭包反射和普通函数反射有什么区别?
闭包多了以下方法:
isClosure()— 判断是否是闭包getClosureScopeClass()— 获取绑定的类getClosureThis()— 获取绑定的对象实例getStaticVariables()— 获取 use 变量bindTo()— 创建新的闭包实例并重新绑定
ReflectionFunction 的高级用法
函数的存在性检查与安全调用
php
<?php
declare(strict_types=1);
class SafeCaller
{
/** @var array<string, \ReflectionFunction> */
private array $cache = [];
public function callIfExists(string $functionName, array $args = []): mixed
{
if (!function_exists($functionName)) {
return null;
}
$refFunc = $this->getReflection($functionName);
return $refFunc->invokeArgs($args);
}
public function getReflection(string $functionName): \ReflectionFunction
{
if (!isset($this->cache[$functionName])) {
$this->cache[$functionName] = new \ReflectionFunction($functionName);
}
return $this->cache[$functionName];
}
public function getSignature(string $functionName): string
{
if (!function_exists($functionName)) {
return "function {$functionName}() { /* 不存在 */ }";
}
$ref = $this->getReflection($functionName);
$modifiers = $ref->isInternal() ? '/* internal */ ' : '';
$returnType = $ref->getReturnType();
$return = $returnType ? ': ' . (string) $returnType : '';
$params = [];
foreach ($ref->getParameters() as $param) {
$str = '';
$type = $param->getType();
if ($type) $str .= (string) $type . ' ';
if ($param->isPassedByReference()) $str .= '&';
if ($param->isVariadic()) $str .= '...';
$str .= '$' . $param->getName();
if ($param->isDefaultValueAvailable()) {
$str .= ' = ' . var_export($param->getDefaultValue(), true);
}
$params[] = $str;
}
return "{$modifiers}function {$ref->getName()}(" . implode(', ', $params) . "){$return}";
}
}
$caller = new SafeCaller();
echo $caller->getSignature('array_map') . "\n";
echo $caller->getSignature('strlen') . "\n";
echo $caller->getSignature('json_encode') . "\n";比较两个函数的签名
php
<?php
declare(strict_types=1);
function compareSignatures(string $funcA, string $funcB): array
{
$refA = new \ReflectionFunction($funcA);
$refB = new \ReflectionFunction($funcB);
return [
'nameA' => $refA->getName(),
'nameB' => $refB->getName(),
'sameParamCount' => $refA->getNumberOfParameters() === $refB->getNumberOfParameters(),
'sameRequiredCount' => $refA->getNumberOfRequiredParameters() === $refB->getNumberOfRequiredParameters(),
'paramA' => $refA->getNumberOfParameters(),
'paramB' => $refB->getNumberOfParameters(),
'requiredA' => $refA->getNumberOfRequiredParameters(),
'requiredB' => $refB->getNumberOfRequiredParameters(),
];
}
print_r(compareSignatures('array_map', 'array_filter'));反射内置函数
php
<?php
declare(strict_types=1);
$builtins = ['strlen', 'array_map', 'json_encode', 'implode', 'preg_match'];
foreach ($builtins as $funcName) {
$ref = new \ReflectionFunction($funcName);
echo "函数: {$ref->getName()}\n";
echo " 内置: " . ($ref->isInternal() ? 'yes' : 'no') . "\n";
echo " 弃用: " . ($ref->isDeprecated() ? 'yes' : 'no') . "\n";
echo " 变长参数: " . ($ref->isVariadic() ? 'yes' : 'no') . "\n";
echo " 返回类型: " . ($ref->getReturnType()?->getName() ?? 'none') . "\n";
echo " 用户定义: " . ($ref->isUserDefined() ? 'yes' : 'no') . "\n";
echo "\n";
}内置函数的反射限制
内置函数的反射无法获取文件名和行号(getFileName() 和 getStartLine() 返回 false),因为它们是编译到 PHP 引擎中的。
常见误区
反射闭包与 bindTo 的配合
php
<?php
declare(strict_types=1);
class EventEmitter
{
private array $listeners = [];
public function on(string $event, callable $callback): void
{
$this->listeners[$event][] = $callback;
}
public function emit(string $event, mixed $data): void
{
foreach ($this->listeners[$event] ?? [] as $listener) {
$listener($data);
}
}
public function getListenerCount(string $event): int
{
return count($this->listeners[$event] ?? []);
}
}
$emitter = new EventEmitter();
$emitter->on('user.created', function ($data): void {
echo "用户已创建: " . json_encode($data) . "\n";
});
// 反射检查 listeners 中的闭包
$refClass = new \ReflectionClass(EventEmitter::class);
$refProp = $refClass->getProperty('listeners');
$listeners = $refProp->getValue($emitter);
if (isset($listeners['user.created'])) {
foreach ($listeners['user.created'] as $index => $closure) {
$refFunc = new \ReflectionFunction($closure);
echo "监听器 #{$index}: " . count($refFunc->getParameters()) . " 个参数\n";
}
}