PHP Attributes 是可执行、可反射、带类型约束的原生语法结构,非注释升级版;定义需显式添加#[Attribute]标记及use Attribute;,并注意目标限制、可重复性、正确位置、反射读取方式及构造函数参数序列化限制。

Attributes 不是注释的“升级版”,而是完全不同的机制:它不依赖字符串解析,不写在 /*<em> </em>/ 里,不能被 IDE 当作文档提示——它是可执行、可反射、带类型约束的原生语法结构。用错地方或误当注释用,运行时直接报错或静默失效。
定义自定义 Attribute 类必须加 #[Attribute]
没这行标记,PHP 就不认它是 Attribute,哪怕类名带 Attribute 后缀也没用。常见错误是只写了 class Route,忘了顶部的 #[Attribute]。
还要注意:use Attribute; 必须显式导入,否则会报 Class "Attribute" not found。PHP 8.0+ 内置该类,但不会自动引入。
-
#[Attribute]默认允许用在任何位置(类、方法、属性等),等价于#[Attribute(Attribute::TARGET_ALL)] - 若只想让
#[Route]只能标在方法上,得写成#[Attribute(Attribute::TARGET_METHOD)] - 想在同一方法上多次使用同一个 Attribute(比如多个验证规则),必须加上
Attribute::IS_REPEATABLE:
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
class Validate { public function __construct(public string $rule) {} }
应用 Attributes 时不能混用 PHPDoc 注释语法
这是最常踩的坑:有人把 #[Route("/api/user")] 错写成 /** #[Route("/api/user")] */,或者塞进已有 /** @param ... */ 块里。PHP 解析器会直接忽略整个 #[] 块——因为它不在合法声明位置。
立即学习“PHP免费学习笔记(深入)”;
正确写法永远是独立一行,紧贴目标元素上方:
#[Route("/users")]
#[Middleware("auth")]
class UserController
{
#[Get("/")]
public function index(): array { /* ... */ }
}
- 不能放在 docblock 里,不能缩进在其他语句后,不能跟在分号/逗号后面
- 多个 Attributes 可以堆叠写,也可以合并成数组形式:
#[Route("/x"), Middleware("log")] - 函数参数上用 Attribute,必须写在参数变量前,不是函数签名末尾:
public function create(#[Validate("email")] string $email): void { ... }
读取 Attributes 必须用 Reflection API,不能靠 getDocComment()
传统注释靠 ReflectionClass->getDocComment() 提取字符串再正则匹配;而 Attributes 必须走 getAttributes() 方法,返回的是 ReflectionAttribute 对象数组。
典型误操作:试图用旧方式解析 #[] ——它根本不会出现在 doc comment 里。
- 获取类上的所有 Attributes:
(new ReflectionClass(UserController::class))->getAttributes() - 按类型过滤:
->getAttributes(Route::class) - 实例化具体对象:
->newInstance(),这步才真正调用你的__construct()
注意:newInstance() 会触发构造函数执行,如果里面含副作用(如 DB 查询、日志写入),要小心重复调用风险。
Attribute 构造函数参数必须可序列化,且无运行时依赖
Attribute 类本质是元数据容器,不是业务逻辑载体。它的构造参数会在反射阶段被还原,所以只支持标量、数组、null、以及实现了 __serialize() 的对象(极少用)。
以下写法全部非法:
-
public function __construct(private PDO $db)→ PDO 不可序列化 -
public function __construct(private callable $validator)→ 闭包无法持久化 -
public function __construct(private DateTime $when = new DateTime())→ 默认参数不允许调用函数
正确做法是只传配置值,把逻辑延迟到实际使用时再注入依赖:
#[Validate(rule: "min:8", message: "Password too short")] public string $password = '';
PHP 8 的 Attributes 真正难的不是语法,而是思维切换:它不是“写给人看的说明”,而是“写给反射引擎读的配置”。一旦当成注释来写、当成注释来读、或在里面塞业务逻辑,后续维护成本会指数上升。



















