Skip to content

ReflectionFunction / ReflectionParameter

概述

ReflectionFunction 用于检查独立函数和闭包的信息,ReflectionParameter 用于检查函数/方法的参数信息。两者配合使用,可以在运行时获取函数的完整签名,包括参数名称、类型、默认值、是否可变参数等。对于闭包,还可以通过反射获取其绑定的作用域和变量。

扩展关系

ReflectionFunctionReflectionMethod 都继承自 ReflectionFunctionAbstract,共享大部分方法。

基础概念

ReflectionFunction

检查函数的名称、参数、返回类型、文件位置等。

ReflectionParameter

检查参数的名称、类型、是否有默认值、是否通过引用传递等。

闭包反射

闭包的 ReflectionFunction 可以检查其绑定的 $thisuse 变量。

语法与代码

检查函数信息

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

特性ReflectionFunctionReflectionMethod
目标独立函数、闭包类方法
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

最佳实践

  1. 使用函数签名验证:通过反射验证回调函数的签名是否符合预期。
  2. 缓存反射结果

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";
    }
}

参考链接