Skip to content

ReflectionMethod

概述

ReflectionMethod 是 PHP 反射 API 中用于检查类方法的类。它扩展自 ReflectionFunctionAbstract,提供了方法签名、参数、修饰符、调用等完整信息。通过 ReflectionMethod,可以在运行时获取方法的访问控制级别、参数列表、返回类型等信息,甚至可以直接调用方法。

核心能力

ReflectionMethod 可以检查和调用任何方法,包括 private 和 protected 方法。

基础概念

获取方法信息

通过 ReflectionClass::getMethod()ReflectionClass::getMethods() 获取 ReflectionMethod 实例。

方法签名

包括方法名、参数列表、返回类型、修饰符等。

invoke / invokeArgs

通过 invoke()invokeArgs() 方法可以在运行时调用反射的方法。

语法与代码

获取方法的基本信息

php
<?php
declare(strict_types=1);

class OrderService
{
    public function __construct(private readonly string $storeName = 'default') {}

    public function create(array $data): int
    {
        return 1;
    }

    protected function validate(array $data): bool
    {
        return true;
    }

    private static function formatId(int $id): string
    {
        return sprintf('ORD-%06d', $id);
    }
}

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

// 获取特定方法
$method = $refClass->getMethod('create');
echo "方法名: " . $method->getName() . "\n";
echo "类名: " . $method->getDeclaringClass()->getName() . "\n";
echo "返回类型: " . $method->getReturnType()?->getName() . "\n";
echo "是否为公共方法: " . ($method->isPublic() ? 'yes' : 'no') . "\n";
echo "是否为静态方法: " . ($method->isStatic() ? 'yes' : 'no') . "\n";
echo "是否为抽象方法: " . ($method->isAbstract() ? 'yes' : 'no') . "\n";

获取方法的修饰符

php
<?php
declare(strict_types=1);

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

foreach ($refClass->getMethods() as $method) {
    $modifiers = \Reflection::getModifierNames($method->getModifiers());
    echo implode(' ', $modifiers) . ' ' . $method->getName() . "()\n";
}
// public __construct()
// public create()
// protected validate()
// private static formatId()

获取方法的参数列表

php
<?php
declare(strict_types=1);

$method = new \ReflectionMethod(OrderService::class, 'create');

echo "参数数量: " . $method->getNumberOfParameters() . "\n";
echo "必需参数: " . $method->getNumberOfRequiredParameters() . "\n";

echo "\n参数详情:\n";
foreach ($method->getParameters() as $param) {
    $type = $param->getType()?->getName() ?? 'mixed';
    $default = $param->isDefaultValueAvailable()
        ? '= ' . var_export($param->getDefaultValue(), true)
        : '(required)';
    echo "  {$type} \${$param->getName()} {$default}\n";
}

使用 invoke 调用方法

php
<?php
declare(strict_types=1);

class Calculator
{
    public function add(int $a, int $b): int
    {
        return $a + $b;
    }

    private function multiply(int $a, int $b): int
    {
        return $a * $b;
    }
}

$calc = new Calculator();

// 调用公共方法
$refMethod = new \ReflectionMethod(Calculator::class, 'add');
$result = $refMethod->invoke($calc, 3, 5);
echo "3 + 5 = {$result}\n"; // 8

// 调用私有方法
$privateMethod = new \ReflectionMethod(Calculator::class, 'multiply');
$result = $privateMethod->invoke($calc, 4, 6);
echo "4 * 6 = {$result}\n"; // 24

使用 invokeArgs 调用方法

php
<?php
declare(strict_types=1);

$refMethod = new \ReflectionMethod(Calculator::class, 'add');
$result = $refMethod->invokeArgs($calc, [10, 20]);
echo "10 + 20 = {$result}\n"; // 30

详细说明

ReflectionMethod 常用方法

方法说明
getName()方法名
getDeclaringClass()声明该方法的类
getModifiers()修饰符位掩码
getParameters()参数列表
getReturnType()返回类型
isPublic() / isProtected() / isPrivate()访问级别
isStatic()是否静态
isAbstract()是否抽象
isFinal()是否 final
isConstructor()是否构造函数
invoke() / invokeArgs()调用方法

PHP 8.1+ 的 setAccessible 变化

PHP 8.1+ 中,setAccessible(true) 不再是必需的——所有反射方法默认可访问。

实战示例

实战:方法调用日志记录器

php
<?php
declare(strict_types=1);

class MethodLogger
{
    public function logMethodCalls(object $object, string $methodName, array $args): mixed
    {
        $refMethod = new \ReflectionMethod($object, $methodName);
        $params = $refMethod->getParameters();

        echo "调用: {$refMethod->getDeclaringClass()->getShortName()}::{$methodName}(\n";
        foreach ($params as $i => $param) {
            $value = $args[$i] ?? '...';
            echo "  {$param->getName()}: " . var_export($value, true) . "\n";
        }
        echo ")\n";

        $start = microtime(true);
        $result = $refMethod->invokeArgs($object, $args);
        $elapsed = (microtime(true) - $start) * 1000;

        echo "返回: " . var_export($result, true) . "\n";
        echo "耗时: {$elapsed}ms\n\n";

        return $result;
    }
}

注意事项

调用静态方法

调用静态方法时,invoke() 的第一个参数应为 null 或类名字符串。

构造方法的反射

php
<?php
declare(strict_types=1);

$constructor = new \ReflectionMethod(OrderService::class, '__construct');
echo $constructor->isConstructor() ? 'yes' : 'no'; // yes

最佳实践

  1. 使用 invokeArgs 传递参数数组:当参数以数组形式存在时更方便。
  2. 检查方法可访问性:调用前检查方法是否存在和可访问。
  3. 避免频繁反射:缓存 ReflectionMethod 实例。
php
<?php
declare(strict_types=1);

function safeInvoke(object $object, string $method, array $args = []): mixed
{
    if (!method_exists($object, $method)) {
        throw new \InvalidArgumentException("方法不存在: {$method}");
    }
    return (new \ReflectionMethod($object, $method))->invokeArgs($object, $args);
}

ReflectionMethod 高级用法

获取方法的所有信息

php
<?php
declare(strict_types=1);

class ReportGenerator
{
    final public function generate(
        string $format,
        array $data,
        callable $filter = null,
    ): string {
        return '';
    }

    abstract protected function validate(array $data): bool;

    private static function formatNumber(float $num): string
    {
        return number_format($num, 2);
    }
}

function dumpMethodInfo(string $className, string $methodName): array
{
    $method = new \ReflectionMethod($className, $methodName);

    return [
        'name' => $method->getName(),
        'class' => $method->getDeclaringClass()->getName(),
        'modifiers' => \Reflection::getModifierNames($method->getModifiers()),
        'returnType' => $method->getReturnType()?->getName(),
        'parameters' => array_map(
            fn(\ReflectionParameter $p) => [
                'name' => $p->getName(),
                'type' => $p->getType()?->getName(),
                'hasDefault' => $p->isDefaultValueAvailable(),
                'default' => $p->isDefaultValueAvailable()
                    ? var_export($p->getDefaultValue(), true)
                    : null,
                'byReference' => $p->isPassedByReference(),
                'variadic' => $p->isVariadic(),
            ],
            $method->getParameters()
        ),
        'isFinal' => $method->isFinal(),
        'isAbstract' => $method->isAbstract(),
        'isStatic' => $method->isStatic(),
        'isConstructor' => $method->isConstructor(),
        'isDestructor' => $method->isDestructor(),
    ];
}

print_r(dumpMethodInfo(ReportGenerator::class, 'generate'));

检查方法重写

php
<?php
declare(strict_types=1);

class BaseService
{
    public function process(array $data): array
    {
        return $data;
    }

    protected function log(string $message): void
    {
        echo $message . "\n";
    }
}

class ExtendedService extends BaseService
{
    public function process(array $data): array
    {
        $this->log('Processing data');
        return array_map(fn($item) => strtoupper($item), $data);
    }
}

function isMethodOverride(string $childClass, string $parentClass, string $method): bool
{
    $childMethod = new \ReflectionMethod($childClass, $method);
    $parentMethod = new \ReflectionMethod($parentClass, $method);
    return $childMethod->getDeclaringClass()->getName()
        !== $parentMethod->getDeclaringClass()->getName();
}

echo isMethodOverride(ExtendedService::class, BaseService::class, 'process') ? 'yes' : 'no';

调用私有构造函数(单例模式测试)

php
<?php
declare(strict_types=1);

class Singleton
{
    private static ?self $instance = null;

    private function __construct() {}

    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }
}

// 测试时需要重置单例
function resetSingleton(): void
{
    $refClass = new \ReflectionClass(Singleton::class);
    $prop = $refClass->getProperty('instance');
    $prop->setValue(null, null);
}

// 创建新实例(绕过私有构造函数)
function createNewSingleton(): Singleton
{
    $refClass = new \ReflectionClass(Singleton::class);
    return $refClass->newInstanceWithoutConstructor();
}

常见误区与 FAQ

ReflectionMethod 可以获取 closure(闭包)吗?

不能。ReflectionMethod 只适用于类方法。对于闭包,应使用 ReflectionFunction

如何调用构造函数中的方法?

ReflectionMethodinvoke() 需要一个对象实例。如果需要在构造之前调用方法,可以使用 newInstanceWithoutConstructor() 创建对象。

invoke 和直接调用有什么性能差异?

invoke 有反射开销,但现代 PHP 中这个开销非常小(微秒级别)。在测试代码中完全可以接受,在高频调用的生产代码中应谨慎使用。

反射方法的高级分析

获取方法的完整元信息

php
<?php
declare(strict_types=1);

class PaymentService
{
    final public function __construct(
        private readonly string $apiKey,
        private readonly string $apiUrl = 'https://api.example.com',
    ) {}

    public function charge(
        float $amount,
        string $currency = 'USD',
        ?string $description = null,
    ): array {
        return ['status' => 'success', 'amount' => $amount];
    }

    protected function validateAmount(float $amount): bool
    {
        return $amount > 0 && $amount <= 1000000;
    }

    private static function formatCurrency(float $amount, string $currency): string
    {
        return sprintf('%s %.2f', $currency, $amount);
    }

    abstract public function refund(int $transactionId): bool;
}

function analyzeMethod(\ReflectionMethod $method): void
{
    $lines = [];
    $lines[] = "方法名: " . $method->getName();
    $lines[] = "声明类: " . $method->getDeclaringClass()->getName();
    $lines[] = "文件: " . ($method->getFileName() ?? 'internal');
    $lines[] = "行号: " . ($method->getStartLine() ?? '') . " - " . ($method->getEndLine() ?? '');

    $returnType = $method->getReturnType();
    $lines[] = "返回类型: " . ($returnType ? (string) $returnType : 'none');

    $modifiers = \Reflection::getModifierNames($method->getModifiers());
    $lines[] = "修饰符: " . implode(' ', $modifiers);

    $lines[] = "特性检查:";
    $lines[] = "  - Final: " . ($method->isFinal() ? 'yes' : 'no');
    $lines[] = "  - Abstract: " . ($method->isAbstract() ? 'yes' : 'no');
    $lines[] = "  - Static: " . ($method->isStatic() ? 'yes' : 'no');
    $lines[] = "  - Public: " . ($method->isPublic() ? 'yes' : 'no');
    $lines[] = "  - Protected: " . ($method->isProtected() ? 'yes' : 'no');
    $lines[] = "  - Private: " . ($method->isPrivate() ? 'yes' : 'no');
    $lines[] = "  - Constructor: " . ($method->isConstructor() ? 'yes' : 'no');
    $lines[] = "  - Destructor: " . ($method->isDestructor() ? 'yes' : 'no');

    echo implode("\n", $lines) . "\n\n";
}

$refClass = new \ReflectionClass(PaymentService::class);
foreach ($refClass->getMethods() as $method) {
    echo "=== 分析: {$method->getName()} ===\n";
    analyzeMethod($method);
}

方法参数的详细反射

php
<?php
declare(strict_types=1);

$method = new \ReflectionMethod(PaymentService::class, 'charge');

echo "参数详情:\n";
foreach ($method->getParameters() as $i => $param) {
    echo "参数 #" . ($i + 1) . ": \${$param->getName()}\n";
    echo "  位置: " . $param->getPosition() . "\n";

    $type = $param->getType();
    if ($type instanceof \ReflectionNamedType) {
        echo "  类型: " . $type->getName();
        if ($type->allowsNull()) echo "|null";
        echo " (builtin: " . ($type->isBuiltin() ? 'yes' : 'no') . ")\n";
    }

    echo "  引用传递: " . ($param->isPassedByReference() ? 'yes' : 'no') . "\n";
    echo "  可变参数: " . ($param->isVariadic() ? 'yes' : 'no') . "\n";
    echo "  必需: " . (!$param->isOptional() ? 'yes' : 'no') . "\n";

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

PHP 8.0+ 命名参数与反射

php
<?php
declare(strict_types=1);

// PHP 8.0+ 命名参数可以通过反射按名称传递
$method = new \ReflectionMethod(PaymentService::class, 'charge');

// 使用 ReflectionMethod::invoke 与命名参数
$service = new PaymentService('sk_test_key');

// invokeArgs 只接受位置参数数组
// 但可以构建正确顺序的数组
$args = [
    0 => 99.99,
    2 => 'Test payment',
];
$result = $method->invokeArgs($service, $args);
print_r($result);

常见误区

如何获取方法的 PHPDoc 注释?

php
<?php
declare(strict_types=1);

$method = new \ReflectionMethod(PaymentService::class, 'charge');
$docComment = $method->getDocComment();

if ($docComment !== false) {
    echo "文档注释:\n{$docComment}\n";
} else {
    echo "无文档注释\n";
}

setAccessible 在 PHP 8.1+ 还需要吗?

PHP 8.1+ 中所有反射方法和属性默认可访问,setAccessible(true) 调用被忽略(不再报错)。这意味着之前的兼容代码无需修改。

参考链接