什么是 PHP Reflection API
PHP Reflection API 是 PHP 内置的能力,可以在运行时获取并检查类、方法、属性、函数、参数等的元信息。可以在不改动源码的情况下探知一个类的构造函数接受哪些参数、方法上挂了哪些属性等等。
Laravel 在 Illuminate/Container/Container.php 中大量使用了 Reflection API,实现了 DI 容器的自动解析、PHP 属性的读取、方法注入等能力。
主要类
| 类 | 主要用途 |
|---|
ReflectionClass | 获取类元信息的入口 |
ReflectionMethod | 获取方法的参数、访问修饰符、属性 |
ReflectionProperty | 获取属性的类型、默认值、attribute |
ReflectionParameter | 获取方法、函数参数信息(类型提示、默认值) |
ReflectionFunction | 获取函数、闭包的元信息 |
ReflectionAttribute | 获取属性的类名与参数 |
ReflectionClass — 获取类信息
$ref = new ReflectionClass(UserController::class);
$ref->getName(); // 完全限定类名
$ref->getShortName(); // 仅类名
$ref->isInstantiable(); // 是否可实例化
$ref->getConstructor(); // 返回构造函数(ReflectionMethod)
$ref->getMethods(); // 返回所有方法 ReflectionMethod[]
$ref->getProperties(); // 返回所有属性 ReflectionProperty[]
$ref->getAttributes(); // 返回类上的属性 ReflectionAttribute[]
ReflectionParameter — 检查构造函数参数
$ref = new ReflectionClass(UserController::class);
$constructor = $ref->getConstructor();
if ($constructor) {
foreach ($constructor->getParameters() as $param) {
$param->getName(); // 参数名
$param->getType(); // 类型提示(ReflectionType)
$param->isOptional(); // 是否可省略
$param->isVariadic(); // 是否可变参数
$param->getDefaultValue(); // 默认值(若存在)
}
}
Laravel 容器与 Reflection API
Laravel 的 IoC 容器利用 Reflection API 实现构造函数注入(依赖自动解析)。理解一下 app()->make(SomeClass::class) 与依赖注入的工作原理。
容器的 build() 方法(简化版)
Container.php 中真正的 build() 方法大致是这样的:
// Illuminate\Container\Container::build() 简化
public function build($concrete)
{
// 1. 用 ReflectionClass 检查类
$reflector = new ReflectionClass($concrete);
// 无法实例化的类(interface、abstract 等)报错
if (! $reflector->isInstantiable()) {
throw new BindingResolutionException("[$concrete] is not instantiable.");
}
// 2. 获取构造函数
$constructor = $reflector->getConstructor();
// 没有构造函数 → 直接实例化
if (is_null($constructor)) {
return new $concrete;
}
// 3. 获取构造函数全部参数
$dependencies = $constructor->getParameters();
// 4. 递归解析每个参数
$instances = $this->resolveDependencies($dependencies);
return new $concrete(...$instances);
}
protected function resolveDependencies(array $dependencies): array
{
$results = [];
foreach ($dependencies as $dependency) {
// 能取到类型提示时通过容器递归解析
$className = Util::getParameterClassName($dependency);
$results[] = is_null($className)
? $this->resolvePrimitive($dependency) // 原始类型
: $this->resolveClass($dependency, $className); // 类类型
}
return $results;
}
Util::getParameterClassName() 是从 $parameter->getType() 提取类型名字符串的工具。它将 ReflectionParameter::getType() 返回的 ReflectionNamedType 做了便捷封装。
读取 PHP 属性
PHP 8.0 及以后,可以通过 Reflection API 读取类、方法、属性上挂的 PHP 属性。Laravel 利用该机制来处理队列属性与 Eloquent 属性。
关于 PHP 属性及其在 Laravel 中的集成,请一并参考 PHP 属性。
读取属性的基本模式
use ReflectionClass;
// 1. 读取类上的属性
$ref = new ReflectionClass(ProcessOrder::class);
$attrs = $ref->getAttributes(Queue::class); // 仅获取特定属性
foreach ($attrs as $attr) {
$instance = $attr->newInstance(); // 实例化属性类
echo $instance->queue; // 读取属性字段
}
// 2. 获取所有属性(不过滤)
$allAttrs = $ref->getAttributes();
foreach ($allAttrs as $attr) {
echo $attr->getName(); // 属性类名(FQCN)
print_r($attr->getArguments()); // 构造函数参数
}
Laravel 读取 Queue 属性的机制(简化)
// 从 InteractsWithQueue 的 ReadsQueueAttributes 简化
protected function setJobInstanceForQueue(object $job): void
{
$reflection = new ReflectionClass($job);
foreach ($reflection->getAttributes(Queue::class) as $attribute) {
$instance = $attribute->newInstance();
$job->queue = $instance->queue instanceof \UnitEnum
? $instance->queue->value
: $instance->queue;
}
}
读取方法上的属性
$ref = new ReflectionClass(UserController::class);
foreach ($ref->getMethods() as $method) {
$attrs = $method->getAttributes(Route::class);
foreach ($attrs as $attr) {
$route = $attr->newInstance();
echo "{$method->getName()} => {$route->path}";
}
}
包开发中的应用示例
检查类实现的接口
在包中可以动态确认某个类是否实现了特定接口。
use ReflectionClass;
function isQueueable(string $class): bool
{
$ref = new ReflectionClass($class);
return $ref->implementsInterface(\Illuminate\Contracts\Queue\ShouldQueue::class);
}
元数据获取 — 通过属性自动注册路由
结合属性与 Reflection 自动收集路由的模式。
// 自定义 Route 属性定义
#[\Attribute(\Attribute::TARGET_METHOD)]
class Route
{
public function __construct(
public string $method,
public string $path,
) {}
}
// 在 Controller 中使用
class UserController
{
#[Route('GET', '/users')]
public function index() { /* ... */ }
#[Route('POST', '/users')]
public function store() { /* ... */ }
}
// 从属性自动注册路由的 Service Provider
class AttributeRouteServiceProvider extends ServiceProvider
{
public function boot(Router $router): void
{
$ref = new ReflectionClass(UserController::class);
foreach ($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
foreach ($method->getAttributes(Route::class) as $attr) {
$route = $attr->newInstance();
$router->addRoute(
$route->method,
$route->path,
[UserController::class, $method->getName()],
);
}
}
}
}
动态方法调用 — 方法注入
Laravel 的 call() 方法利用 Reflection 自动解析参数。以下是在包中实现相同机制的示例。
use ReflectionFunction;
use ReflectionMethod;
function callWithDependencies(callable $callable, Container $container): mixed
{
if (is_array($callable)) {
[$object, $method] = $callable;
$ref = new ReflectionMethod($object, $method);
$params = $ref->getParameters();
} else {
$ref = new ReflectionFunction($callable);
$params = $ref->getParameters();
}
$args = [];
foreach ($params as $param) {
$type = $param->getType()?->getName();
$args[] = $type ? $container->make($type) : null;
}
return $callable(...$args);
}
// 使用示例
callWithDependencies([new UserController(), 'index'], app());
收集属性的默认值
通过 Reflection 获取配置类的默认值的模式。
use ReflectionClass;
use ReflectionProperty;
function getDefaults(string $class): array
{
$ref = new ReflectionClass($class);
$defaults = [];
foreach ($ref->getProperties(ReflectionProperty::IS_PUBLIC) as $prop) {
if ($prop->hasDefaultValue()) {
$defaults[$prop->getName()] = $prop->getDefaultValue();
}
}
return $defaults;
}
class DatabaseConfig
{
public string $driver = 'mysql';
public int $port = 3306;
public bool $strict = true;
}
// ['driver' => 'mysql', 'port' => 3306, 'strict' => true]
$defaults = getDefaults(DatabaseConfig::class);
性能方面的注意
Reflection API 每次都会解析类信息,成本较高。生产代码中缓存结果是最佳实践。
class ReflectionCache
{
private static array $cache = [];
public static function getClass(string $class): \ReflectionClass
{
return self::$cache[$class] ??= new \ReflectionClass($class);
}
}
// 使用示例
$ref = ReflectionCache::getClass(UserController::class);
PHP 的 OPcache 不会缓存 Reflection 结果。若在循环中检查大量类,请考虑自定义缓存。不过 Laravel 容器本身在同一请求内会复用 ReflectionClass 实例。
下一步
PHP 属性
学习通过 ReflectionClass::getAttributes() 读取 PHP 属性的详情。
包开发
了解利用 Reflection API 的 Laravel 包开发方法。