ReflectionAttribute
概述
ReflectionAttribute 是 PHP 8.0 引入的反射类,用于在运行时读取代码结构(类、方法、属性、函数、参数等)上的注解(Attributes)。通过 ReflectionAttribute,你可以获取注解的名称、参数,并实例化为真正的注解对象。
核心方法
getAttributes() — 获取所有注解,newInstance() — 实例化注解对象,getArguments() — 获取原始参数。
基础概念
获取注解反射对象
通过 getAttributes() 方法从 ReflectionClass、ReflectionMethod、ReflectionProperty 等获取 ReflectionAttribute 实例。
注解实例化
newInstance() 调用注解类的构造函数,创建注解对象实例。
参数映射
getArguments() 返回注解声明时的参数数组,与构造函数参数一一对应。
语法与代码
获取所有注解
php
<?php
declare(strict_types=1);
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class Route
{
public function __construct(
public readonly string $path,
public readonly string $method = 'GET'
) {}
}
class PostController
{
#[Route(path: '/posts', method: 'GET')]
public function index(): array { return []; }
#[Route(path: '/posts', method: 'POST')]
public function store(): array { return []; }
}
$refClass = new \ReflectionClass(PostController::class);
foreach ($refClass->getMethods() as $method) {
$attrs = $method->getAttributes();
foreach ($attrs as $attr) {
echo "方法 {$method->getName()} -> 注解: " . $attr->getName() . "\n";
echo " 参数: " . json_encode($attr->getArguments()) . "\n";
}
}获取指定类型的注解
php
<?php
declare(strict_types=1);
$refMethod = new \ReflectionMethod(PostController::class, 'index');
$routeAttrs = $refMethod->getAttributes(Route::class);
foreach ($routeAttrs as $attr) {
$route = $attr->newInstance();
echo "路径: {$route->path}, 方法: {$route->method}\n";
}
// 路径: /posts, 方法: GETgetArguments 与 newInstance 对比
php
<?php
declare(strict_types=1);
$refMethod = new \ReflectionMethod(PostController::class, 'store');
$attrs = $refMethod->getAttributes(Route::class);
$attr = $attrs[0];
// getArguments() — 返回原始参数数组
$args = $attr->getArguments();
print_r($args);
// Array ( [path] => /posts [method] => POST )
// newInstance() — 创建注解实例
$instance = $attr->newInstance();
echo $instance->path; // /posts
echo $instance->method; // POST接口过滤
php
<?php
declare(strict_types=1);
interface ValidationRule {}
#[Attribute(Attribute::TARGET_PROPERTY)]
class Required implements ValidationRule
{
public string $message = '此字段为必填项';
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class MaxLength implements ValidationRule
{
public function __construct(public readonly int $value) {}
}
class UserForm
{
#[Required]
#[MaxLength(value: 50)]
public string $username;
}
$refProp = new \ReflectionProperty(UserForm::class, 'username');
// 使用 IS_INSTANCEOF 获取所有实现 ValidationRule 的注解
$rules = $refProp->getAttributes(
ValidationRule::class,
\ReflectionAttribute::IS_INSTANCEOF
);
foreach ($rules as $rule) {
echo $rule->getName() . "\n";
// Required
// MaxLength
}获取类、方法、属性上的注解
php
<?php
declare(strict_types=1);
#[Attribute(Attribute::TARGET_CLASS)]
class Table { public function __construct(public readonly string $name) {} }
#[Attribute(Attribute::TARGET_PROPERTY)]
class Column { public function __construct(public readonly string $type) {} }
#[Table(name: 'products')]
class Product
{
#[Column(type: 'string')]
public string $name;
#[Column(type: 'integer')]
public int $price;
}
// 类注解
$classAttrs = (new \ReflectionClass(Product::class))->getAttributes(Table::class);
$table = $classAttrs[0]->newInstance();
echo "表名: {$table->name}\n"; // products
// 属性注解
foreach ((new \ReflectionClass(Product::class))->getProperties() as $prop) {
$colAttrs = $prop->getAttributes(Column::class);
foreach ($colAttrs as $colAttr) {
$col = $colAttr->newInstance();
echo "{$prop->getName()}: {$col->type}\n";
}
}详细说明
ReflectionAttribute 方法
| 方法 | 说明 | 返回值 |
|---|---|---|
getName() | 获取注解类名 | string |
getArguments() | 获取注解参数 | array |
newInstance() | 实例化注解 | object |
newInstanceArgs($args) | 使用自定义参数实例化 | object |
getAttributes 的过滤器
php
<?php
declare(strict_types=1);
// 获取所有注解
$allAttrs = $ref->getAttributes();
// 获取指定类名的注解
$specificAttrs = $ref->getAttributes(Route::class);
// 获取实现指定接口的所有注解
$interfaceAttrs = $ref->getAttributes(
SomeInterface::class,
\ReflectionAttribute::IS_INSTANCEOF
);实战示例
实战:注解驱动的验证框架
php
<?php
declare(strict_types=1);
use Attribute;
#[Attribute(Attribute::TARGET_PROPERTY)]
class Required {}
#[Attribute(Attribute::TARGET_PROPERTY)]
class Length
{
public function __construct(
public readonly int $min = 0,
public readonly int $max = PHP_INT_MAX
) {}
}
#[Attribute(Attribute::TARGET_PROPERTY)]
class Email {}
class Validator
{
public function validate(object $data): array
{
$errors = [];
$refClass = new \ReflectionClass($data);
foreach ($refClass->getProperties() as $prop) {
$value = $prop->getValue($data);
$propName = $prop->getName();
foreach ($prop->getAttributes() as $attr) {
$rule = $attr->newInstance();
if ($rule instanceof Required && ($value === null || $value === '')) {
$errors[$propName][] = "{$propName} 是必填项";
}
if ($rule instanceof Length) {
$len = is_string($value) ? strlen($value) : 0;
if ($len < $rule->min) {
$errors[$propName][] = "{$propName} 至少需要 {$rule->min} 个字符";
}
if ($len > $rule->max) {
$errors[$propName][] = "{$propName} 最多 {$rule->max} 个字符";
}
}
if ($rule instanceof Email && !filter_var($value, FILTER_VALIDATE_EMAIL)) {
$errors[$propName][] = "{$propName} 不是有效的邮箱地址";
}
}
}
return $errors;
}
}注意事项
newInstance() 的异常传播
如果注解类的构造函数抛出异常,newInstance() 会传播该异常。
性能优化
反射读取注解有性能开销。建议在应用启动时一次性解析并缓存。
最佳实践
- 使用 getAttributes(Type::class):指定具体类型而非获取所有注解。
- 缓存注解实例:注解解析结果通常可以缓存。
- 异常处理:处理 newInstance() 可能抛出的异常。
php
<?php
declare(strict_types=1);
function getRouteInfo(string $class, string $method): ?object
{
try {
$ref = new \ReflectionMethod($class, $method);
$attrs = $ref->getAttributes(Route::class);
if (empty($attrs)) {
return null;
}
return $attrs[0]->newInstance();
} catch (\Throwable $e) {
return null;
}
}ReflectionAttribute 高级用法
获取所有目标的注解
php
<?php
declare(strict_types=1);
use Attribute;
#[Attribute(Attribute::TARGET_ALL | Attribute::IS_REPEATABLE)]
readonly class Log
{
public function __construct(
public string $level = 'info',
public string $message = '',
) {}
}
#[Log(level: 'info', message: 'Application started')]
#[Log(level: 'debug', message: 'Initializing components')]
class ApplicationService
{
#[Log(level: 'info', message: 'Processing request')]
public function handle(array $data): array
{
return $data;
}
#[Log(level: 'warning', message: 'Validation may fail')]
#[Log(level: 'error', message: 'Exception possible')]
public function validate(array $data): bool
{
return true;
}
}
// 获取类上的所有注解
$refClass = new \ReflectionClass(ApplicationService::class);
$classAttrs = $refClass->getAttributes(Log::class);
echo "类注解数量: " . count($classAttrs) . "\n";
foreach ($classAttrs as $attr) {
$log = $attr->newInstance();
echo " [{$log->level}] {$log->message}\n";
}
// 获取方法上的所有注解
foreach ($refClass->getMethods() as $method) {
$methodAttrs = $method->getAttributes(Log::class);
if (!empty($methodAttrs)) {
echo "方法 {$method->getName()} 的注解:\n";
foreach ($methodAttrs as $attr) {
$log = $attr->newInstance();
echo " [{$log->level}] {$log->message}\n";
}
}
}getArguments 和 newInstanceArgs
getArguments() 返回原始参数数组,newInstanceArgs() 允许传递额外参数:
php
<?php
declare(strict_types=1);
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
class RateLimit
{
public function __construct(
public int $maxRequests = 60,
public int $perSeconds = 60,
) {}
}
class ApiController
{
#[RateLimit(maxRequests: 100, perSeconds: 60)]
public function list(): array { return []; }
}
$method = new \ReflectionMethod(ApiController::class, 'list');
$attrs = $method->getAttributes(RateLimit::class);
$attr = $attrs[0];
// 获取原始参数
$args = $attr->getArguments();
print_r($args);
// ['maxRequests' => 100, 'perSeconds' => 60]
// 创建实例(与 newInstance 相同)
$instance = $attr->newInstance();
echo "最大请求: {$instance->maxRequests}\n";
echo "每秒: {$instance->perSeconds}\n";注解反射在框架路由中的应用
php
<?php
declare(strict_types=1);
use Attribute;
#[Attribute(Attribute::TARGET_METHOD)]
readonly class Route
{
public function __construct(
public string $path,
public string $method = 'GET',
public string $name = '',
) {}
}
class UserController
{
#[Route(path: '/users', method: 'GET', name: 'user.list')]
public function list(): array { return []; }
#[Route(path: '/users/{id}', method: 'GET', name: 'user.show')]
public function show(int $id): array { return []; }
#[Route(path: '/users', method: 'POST', name: 'user.create')]
public function create(): array { return []; }
#[Route(path: '/users/{id}', method: 'DELETE', name: 'user.delete')]
public function delete(int $id): void {}
}
// 路由收集器
function collectRoutes(string $controllerClass): array
{
$routes = [];
$refClass = new \ReflectionClass($controllerClass);
foreach ($refClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
$attrs = $method->getAttributes(Route::class);
foreach ($attrs as $attr) {
$route = $attr->newInstance();
$routes[] = [
'name' => $route->name,
'path' => $route->path,
'method' => $route->method,
'handler' => "{$controllerClass}::{$method->getName()}",
'parameters' => array_map(
fn(\ReflectionParameter $p) => $p->getName(),
$method->getParameters()
),
];
}
}
return $routes;
}
print_r(collectRoutes(UserController::class));常见误区与 FAQ
getAttributes 可以不传类名参数吗?
可以。不传类名时返回所有注解。传入类名时只返回匹配的注解。传入多个类名可以通过数组形式传入。
newInstance 和 newInstanceArgs 有什么区别?
newInstance()使用注解的原始参数创建实例newInstanceArgs($args)使用传入的参数创建实例
通常使用 newInstance() 即可。
如何获取注解的声明位置?
ReflectionAttribute::getTarget() 返回注解的目标类型(类、方法、属性、参数等)。