PHP Attributes 注解
PHP Attributes ,php8.0之后的特性。
PHP Attributes(注解)是 PHP 8.0 引入的一项重要特性,用于在代码中以结构化、类型安全的方式为类、方法、属性、函数、参数等添加元数据(metadata)。它取代了过去通过注释(如 DocBlock)来传递元数据的“约定式”做法,使元数据成为语言的一等公民。
一、什么是 PHP Attributes?
Attributes(注解) 是一种原生语法,允许开发者将附加信息(元数据)直接附加到程序元素上,例如:
- 类(class)
- 方法(method)
- 属性(property)
- 函数(function)
- 参数(parameter)
- 常量(constant)
这些元数据可以在运行时通过反射(Reflection)读取和使用。
✅ 关键点:Attributes 不是注释,而是可被解析和使用的实际代码结构。
二、为什么引入 Attributes?
在 PHP 8 之前,框架(如 Doctrine、Symfony)广泛使用 DocBlock 注解(Annotations) 来实现 ORM 映射、路由定义、验证规则等功能。例如:
/** * @Entity * @Table(name="users") */ class User {}
但这类注解存在以下问题:
- 非标准语法(依赖第三方库解析)
- 无类型检查
- IDE 支持有限
- 容易拼写错误且难以调试
PHP Attributes 提供了原生、类型安全、IDE 友好的替代方案。
三、Attribute 的基本语法
1. 定义一个 Attribute
使用 #[Attribute] 标记一个类为属性类:
<?php use Attribute; #[Attribute] class MyAttribute { public function __construct(public string $value = '') {} }
注意:必须导入
Attribute类(use Attribute;),它是 PHP 内置类。
2. 使用 Attribute
#[MyAttribute('Hello')] class MyClass { #[MyAttribute('World')] public string $name = ''; }
3. 多个 Attribute
可以堆叠多个:
#[MyAttribute('A')] #[MyAttribute('B')] class Test {}
或使用数组形式(PHP 8.0+):
#[MyAttribute('A'), MyAttribute('B')] class Test {}
四、Attribute 的目标(Targets)
你可以限制 Attribute 只能用在特定位置,通过 flags 参数:
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)] class Route { public function __construct(public string $path) {} }
常用目标常量:
| 常量 | 说明 |
|---|---|
TARGET_CLASS |
类 |
TARGET_FUNCTION |
函数 |
TARGET_METHOD |
方法 |
TARGET_PROPERTY |
属性 |
TARGET_PARAMETER |
参数 |
TARGET_CLASS_CONSTANT |
类常量 |
IS_REPEATABLE |
允许重复使用 |
示例:只允许用于方法,并可重复:
五、运行时读取 Attributes(反射)
使用 PHP 的反射 API 获取 Attributes:
$reflection = new ReflectionClass(MyClass::class); $attributes = $reflection->getAttributes(); foreach ($attributes as $attribute) { // 获取 Attribute 实例 $instance = $attribute->newInstance(); echo $instance->value; // 输出 'Hello' }
⚠️ 注意:
newInstance()会实例化 Attribute 类,因此构造函数参数必须匹配。
也可以获取原始参数而不实例化:
$attr = $reflection->getAttributes(MyAttribute::class)[0]; $args = $attr->getArguments(); // ['Hello']
六、实际应用示例
1. 方法参数上写注解,进行参数校验
#[Attribute(Attribute::TARGET_PARAMETER)] class MaxLength { public function __construct(public int $length) {} } function greet(#[MaxLength(10)] string $name) { // 验证逻辑可通过反射在调用前执行,如果匹配失败抛出异常,正常方法中的逻辑先不执行 }
模仿框架中反射处理注解,(直接实例化调用greet方法,注解是完全失效的)
<?php #[Attribute(Attribute::TARGET_PARAMETER)] class MaxLength { public function __construct(public int $length) {} } function greet(#[MaxLength(10)] string $name, string $message) { echo "Hello, {$name}! Message: {$message}\n"; } /** * 通用的函数参数注解校验函数(优化版:显式判断注解是否存在) * @param string $funcName 要调用的函数名 * @param array $args 函数入参数组(按参数顺序传递) * @throws InvalidArgumentException 校验失败时抛出异常 */ function validateFunctionArgs(string $funcName, array $args): void { $reflectionFunc = new ReflectionFunction($funcName); $parameters = $reflectionFunc->getParameters(); foreach ($parameters as $paramIndex => $param) { // 1. 跳过无传入值的参数 if (!isset($args[$paramIndex])) { continue; } // 2. 显式获取 MaxLength 注解,判断是否存在 $maxLengthAttributes = $param->getAttributes(MaxLength::class); // 核心优化:显式判断注解是否存在,不存在则直接跳过当前参数 if (empty($maxLengthAttributes)) { continue; } // 3. 注解存在时,才执行校验逻辑 /** @var MaxLength $maxLengthAttr */ $maxLengthAttr = $maxLengthAttributes[0]->newInstance(); $maxLength = $maxLengthAttr->length; $paramValue = $args[$paramIndex]; // 4. 仅对字符串类型参数做长度校验(避免非字符串参数报错) if (is_string($paramValue) && mb_strlen($paramValue) > $maxLength) { throw new InvalidArgumentException( "参数「{$param->getName()}」长度不能超过 {$maxLength}(当前长度:" . mb_strlen($paramValue) . ")" ); } } } function callFunctionWithValidation(string $funcName, array $args): void { validateFunctionArgs($funcName, $args); call_user_func_array($funcName, $args); } // -------------------------- 测试示例(验证无注解参数跳过) -------------------------- try { // 测试:name加了注解(长度限制10),message无注解(长度超10也不校验) callFunctionWithValidation('greet', ['Alice', '这是一段超过10个字符的消息内容']); // 测试:name长度超10,触发校验异常 callFunctionWithValidation('greet', ['ThisIsALongName123', '正常消息']); } catch (InvalidArgumentException $e) { echo "校验失败:{$e->getMessage()}\n"; }
2. 路由定义(类似 Symfony)
#[Attribute(Attribute::TARGET_METHOD)] class Route { public function __construct( public string $path, public string $method = 'GET' ) {} } class UserController { #[Route('/user', 'GET')] public function list() {} }
路由解析器可通过反射读取 Route 属性注册路由。(具体可以看看下面引用的文章)
3. ORM 映射(类似 Doctrine)
#[Attribute(Attribute::TARGET_PROPERTY)] class Column { public function __construct( public string $type, public bool $nullable = false ) {} } class User { #[Column('string', nullable: false)] public string $email; }
ORM 框架可在启动时扫描这些属性生成数据库映射。
七、与 DocBlock 注解对比
| 特性 | DocBlock 注解 | PHP Attributes |
|---|---|---|
| 语法 | 注释(字符串) | 原生代码 |
| 类型安全 | ❌ | ✅ |
| IDE 支持 | 依赖插件 | 原生支持(自动补全、跳转) |
| 性能 | 需解析字符串 | 直接编译为 AST 节点 |
| 可重复 | 通常支持 | 需显式声明 IS_REPEATABLE |
| 标准化 | 无标准 | PHP 官方标准(RFC) |
八、兼容性与版本要求
- 最低版本:PHP 8.0+
- 不兼容 PHP 7.x(无法降级使用)
九、最佳实践
- 命名清晰:Attribute 类名应表达其用途(如
Route,Cacheable,ValidateEmail)。 - 限制目标:始终使用
TARGET_*限制使用位置,避免误用。 - 避免复杂逻辑:Attribute 类应保持简单,仅存储数据。
- 缓存反射结果:反射较慢,生产环境应缓存解析结果。
十、总结
PHP Attributes 是现代 PHP 开发的重要工具,它:
- 提供了标准化的元数据机制
- 提升了代码可读性与安全性
- 为框架开发带来更强大的能力
- 推动 PHP 向更现代化的语言演进
如果你正在使用 PHP 8+,强烈建议在新项目中优先使用 Attributes 替代传统注解。
📚 官方文档参考:
文档有千问生成
引用 :https://www.cnblogs.com/catchadmin/p/19395584

浙公网安备 33010602011771号