Skip to content

反射概览

概述

反射(Reflection)是 PHP 中强大的运行时自省机制,允许程序在运行时检查类、方法、属性、函数、参数等的结构信息。反射是许多 PHP 框架(如 Laravel、Symfony)的核心基础设施,广泛应用于依赖注入容器、ORM 映射、路由解析、中间件处理等场景。

核心价值

反射让程序能够"了解自己"——在运行时查询和操作代码结构,这是元编程的基础。

基础概念

什么是反射

反射是指在程序运行时获取和操作类、方法、属性、函数等信息的能力。PHP 提供了一套完整的反射 API(Reflection API),包含多个反射类。

反射的核心用途

  • 框架开发:依赖注入容器、路由解析、中间件
  • ORM 映射:将数据库表映射到 PHP 类
  • 调试与分析:检查类结构、方法签名
  • 测试:Mock 对象、测试覆盖率分析
  • 注解处理:读取类/方法上的属性

反射 API 的组成

反射类用途
ReflectionClass类信息
ReflectionMethod方法信息
ReflectionProperty属性信息
ReflectionFunction函数信息
ReflectionParameter参数信息
ReflectionType类型信息
ReflectionAttribute注解信息
ReflectionEnum枚举信息(PHP 8.1+)
ReflectionFiberFiber 信息(PHP 8.1+)

语法与代码

检查类的基本信息

php
<?php
declare(strict_types=1);

class UserService
{
    public function __construct(
        private readonly int $maxRetries = 3
    ) {}

    public function findById(int $id): ?array
    {
        return ['id' => $id, 'name' => 'User'];
    }

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

    private function log(string $message): void {}
}

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

echo "类名: " . $refClass->getName() . "\n";
echo "短名: " . $refClass->getShortName() . "\n";
echo "命名空间: " . $refClass->getNamespaceName() . "\n";
echo "文件: " . $refClass->getFileName() . "\n";
echo "是否为内部类: " . ($refClass->isInternal() ? 'yes' : 'no') . "\n";
echo "是否可实例化: " . ($refClass->isInstantiable() ? 'yes' : 'no') . "\n";

获取类的方法列表

php
<?php
declare(strict_types=1);

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

echo "所有方法:\n";
foreach ($refClass->getMethods() as $method) {
    $modifiers = \Reflection::getModifierNames($method->getModifiers());
    $modStr = implode(' ', $modifiers);
    echo "  {$modStr} {$method->getName()}()\n";
}

echo "\n公共方法:\n";
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
    echo "  " . $method->getName() . "()\n";
}

获取类的属性列表

php
<?php
declare(strict_types=1);

class Product
{
    public string $name;
    protected int $price;
    private bool $active = true;
    public static int $count = 0;
}

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

foreach ($refClass->getProperties() as $property) {
    $modifiers = \Reflection::getModifierNames($property->getModifiers());
    $modStr = implode(' ', $modifiers);
    $type = $property->getType()?->getName() ?? 'mixed';
    echo "{$modStr} {$type} \${$property->getName()}\n";
}

检查函数签名

php
<?php
declare(strict_types=1);

function calculateArea(
    float $width,
    float $height,
    string $unit = 'sqm'
): string {
    return ($width * $height) . ' ' . $unit;
}

$refFunc = new \ReflectionFunction('calculateArea');

echo "函数名: " . $refFunc->getName() . "\n";
echo "文件: " . $refFunc->getFileName() . "\n";
echo "返回类型: " . $refFunc->getReturnType()?->getName() . "\n";

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

创建类的实例

php
<?php
declare(strict_types=1);

class Config
{
    public function __construct(
        public readonly string $app,
        public readonly string $env,
        public readonly bool $debug = false
    ) {}
}

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

// 使用反射创建实例
$instance = $refClass->newInstance('MyApp', 'production', true);
echo $instance->app . "\n"; // MyApp
echo $instance->env . "\n"; // production

详细说明

ReflectionClass 常用方法

方法说明
getName()获取完整类名
getShortName()获取短类名
getNamespaceName()获取命名空间
getFileName()获取定义文件
getMethods()获取所有方法
getProperties()获取所有属性
getConstants()获取所有常量
getConstructor()获取构造函数
isSubclassOf()检查是否为子类
implementsInterface()检查是否实现接口
newInstance()创建实例
newInstanceArgs()使用参数数组创建实例

反射的性能

反射操作有一定的性能开销。在热路径中应缓存反射结果。

php
<?php
declare(strict_types=1);

class ReflectionCache
{
    /** @var array<string, \ReflectionClass> */
    private static array $classCache = [];

    public static function getClass(string $className): \ReflectionClass
    {
        if (!isset(self::$classCache[$className])) {
            self::$classCache[$className] = new \ReflectionClass($className);
        }
        return self::$classCache[$className];
    }
}

实战示例

实战:简单的依赖注入容器

php
<?php
declare(strict_types=1);

class Container
{
    /** @var array<string, object> */
    private array $instances = [];
    /** @var array<string, callable> */
    private array $bindings = [];

    public function bind(string $abstract, callable $concrete): void
    {
        $this->bindings[$abstract] = $concrete;
    }

    public function get(string $abstract): object
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        if (isset($this->bindings[$abstract])) {
            $instance = ($this->bindings[$abstract])($this);
            $this->instances[$abstract] = $instance;
            return $instance;
        }

        return $this->resolve($abstract);
    }

    private function resolve(string $className): object
    {
        $refClass = new \ReflectionClass($className);

        if (!$refClass->isInstantiable()) {
            throw new \RuntimeException("无法实例化: {$className}");
        }

        $constructor = $refClass->getConstructor();

        if ($constructor === null) {
            return $refClass->newInstance();
        }

        $params = [];
        foreach ($constructor->getParameters() as $param) {
            $type = $param->getType();
            if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
                $params[] = $this->get($type->getName());
            } elseif ($param->isDefaultValueAvailable()) {
                $params[] = $param->getDefaultValue();
            } else {
                throw new \RuntimeException("无法解析参数: {$param->getName()}");
            }
        }

        return $refClass->newInstanceArgs($params);
    }
}

注意事项

反射与安全

反射可以访问 private 属性和方法(通过 setAccessible(true)),这打破了封装性。在生产环境中要谨慎使用。

反射与缓存

反射操作有性能开销,频繁使用时应缓存反射实例。

最佳实践

  1. 缓存反射实例:避免在循环中重复创建 ReflectionClass
  2. 使用 setAccessible(true):PHP 8.1+ 中所有 ReflectionPropertyReflectionMethod 默认可访问。
  3. 错误处理:反射操作可能抛出 ReflectionException,注意捕获。
  4. 框架中使用:现代框架已经封装了反射 API,优先使用框架提供的工具。

反射 API 体系

完整的反射类列表

反射类用途PHP 版本
ReflectionClass类信息5.0+
ReflectionMethod方法信息5.0+
ReflectionProperty属性信息5.0+
ReflectionFunction函数信息5.0+
ReflectionParameter参数信息5.0+
ReflectionType类型信息7.0+
ReflectionNamedType命名类型7.0+
ReflectionUnionType联合类型8.0+
ReflectionIntersectionType交集类型8.1+
ReflectionAttribute注解信息8.0+
ReflectionEnum枚举信息8.1+
ReflectionEnumCase枚举 case8.1+
ReflectionClassConstant类常量5.1+
ReflectionExtension扩展信息5.0+
ReflectionFunctionAbstract函数/方法基类5.0+
ReflectionFiberFiber 信息8.1+
ReflectionGenerator生成器信息7.0+
ReflectionReference引用信息8.0+
ReflectionZendExtensionZend 扩展5.0+
ReflectionObject对象反射5.0+

反射类继承关系

Reflection
├── ReflectionClass
├── ReflectionFunctionAbstract
│   ├── ReflectionFunction
│   └── ReflectionMethod
├── ReflectionParameter
├── ReflectionType (interface)
│   ├── ReflectionNamedType
│   ├── ReflectionUnionType (8.0+)
│   └── ReflectionIntersectionType (8.1+)
├── ReflectionAttribute (8.0+)
├── ReflectionEnum (8.1+)
│   └── ReflectionEnumCase (8.1+)
├── ReflectionFiber (8.1+)
├── ReflectionGenerator
└── ReflectionExtension

反射的核心应用场景

1. 依赖注入容器

php
<?php
declare(strict_types=1);

class SimpleContainer
{
    /** @var array<string, object> */
    private array $instances = [];

    /** @var array<string, callable> */
    private array $bindings = [];

    public function bind(string $abstract, callable $concrete): void
    {
        $this->bindings[$abstract] = $concrete;
    }

    public function get(string $abstract): object
    {
        if (isset($this->instances[$abstract])) {
            return $this->instances[$abstract];
        }

        if (isset($this->bindings[$abstract])) {
            $instance = ($this->bindings[$abstract])($this);
            $this->instances[$abstract] = $instance;
            return $instance;
        }

        return $this->resolve($abstract);
    }

    private function resolve(string $className): object
    {
        $refClass = new \ReflectionClass($className);

        if (!$refClass->isInstantiable()) {
            throw new \RuntimeException("{$className} 不可实例化");
        }

        $constructor = $refClass->getConstructor();

        if ($constructor === null) {
            return $refClass->newInstance();
        }

        $params = [];
        foreach ($constructor->getParameters() as $param) {
            $type = $param->getType();
            if ($type instanceof \ReflectionNamedType && !$type->isBuiltin()) {
                $params[] = $this->get($type->getName());
            } else {
                $params[] = $param->getDefaultValue();
            }
        }

        return $refClass->newInstanceArgs($params);
    }
}

2. ORM 映射

反射被广泛用于 ORM 框架中,将数据库表映射到 PHP 类:

php
<?php
declare(strict_types=1);

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
readonly class Column
{
    public function __construct(
        public string $name,
        public string $type = 'string',
    ) {}
}

class User
{
    #[Column(name: 'id', type: 'integer')]
    private int $id = 0;

    #[Column(name: 'username', type: 'string')]
    private string $username = '';

    #[Column(name: 'email', type: 'string')]
    private string $email = '';
}

function getEntityColumns(string $className): array
{
    $refClass = new \ReflectionClass($className);
    $columns = [];

    foreach ($refClass->getProperties() as $prop) {
        $attrs = $prop->getAttributes(Column::class);
        foreach ($attrs as $attr) {
            $column = $attr->newInstance();
            $columns[$prop->getName()] = [
                'column' => $column->name,
                'type' => $column->type,
                'property' => $prop->getName(),
            ];
        }
    }

    return $columns;
}

print_r(getEntityColumns(User::class));

常见误区与 FAQ

反射会影响性能吗?

反射有一定的性能开销,但现代框架通过缓存反射结果来最小化影响。对于高频调用的代码路径,建议缓存反射结果。

反射可以访问 private 成员吗?

PHP 8.1+ 中,反射默认可以访问所有可见性的成员。PHP 8.0 及以下需要调用 setAccessible(true)

反射和 PHPDoc 解析有什么关系?

反射 API 提供的是 PHP 语言级别的元信息(类名、方法名、参数类型等)。PHPDoc 注释的解析需要额外的工具(如 Doctrine Annotations 或 phpDocumentor Reflection)。

参考链接