反射概览
概述
反射(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+) |
| ReflectionFiber | Fiber 信息(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)),这打破了封装性。在生产环境中要谨慎使用。
反射与缓存
反射操作有性能开销,频繁使用时应缓存反射实例。
最佳实践
- 缓存反射实例:避免在循环中重复创建
ReflectionClass。 - 使用
setAccessible(true):PHP 8.1+ 中所有ReflectionProperty和ReflectionMethod默认可访问。 - 错误处理:反射操作可能抛出
ReflectionException,注意捕获。 - 框架中使用:现代框架已经封装了反射 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 | 枚举 case | 8.1+ |
ReflectionClassConstant | 类常量 | 5.1+ |
ReflectionExtension | 扩展信息 | 5.0+ |
ReflectionFunctionAbstract | 函数/方法基类 | 5.0+ |
ReflectionFiber | Fiber 信息 | 8.1+ |
ReflectionGenerator | 生成器信息 | 7.0+ |
ReflectionReference | 引用信息 | 8.0+ |
ReflectionZendExtension | Zend 扩展 | 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)。