annotationcollector::getmethodsbyclass()仅返回指定类自身声明的方法注解,不递归收集父类、接口或trait中的注解;需手动遍历继承链合并,子类优先,且php原生反射与hyperf均不支持注解继承语义。

AnnotationCollector 不会自动合并父类与子类的方法注解
Hyperf 的 AnnotationCollector::getMethodsByClass() 只返回「指定类自身声明」的方法上定义的注解,不会递归向上收集父类、接口或 trait 中同名方法的注解。即使子类方法未重写父类方法,只要没显式标注,就视为无注解。
常见错误现象:@GetMapping 写在父控制器基类的 index() 上,子类继承后访问 /sub/index 404;或者自定义 @Permission 在基类方法中标注,但子类调用时权限校验不生效。
原因在于 AnnotationCollector 的收集是静态、扁平化的:它按文件扫描 AST,只记录当前类节点下明确写出的 DocBlock,不模拟运行时继承链。PHP 反射本身也不提供「继承来的注解」这一概念。
- 父类方法有
@GetMapping("/v1/list"),子类未重写该方法 → 子类实例调用时路由不注册,getMethodsByClass('App\Controller\SubController')返回空数组 - 子类重写了方法但没加任何注解 → 注解数据完全丢失,哪怕父类有同名方法且带注解
- trait 中的方法即使被子类 use 进来,也因 PHP 反射限制(
ReflectionMethod::getAttributes()抛出ReflectionException)无法被扫描到
手动合并注解需自行遍历类继承树
若业务需要「子类自动继承父类方法注解」,必须在运行时手动补全。不能依赖框架默认行为,也不能指望 scan_method => true 自动解决。
核心思路是:拿到目标类,用 class_parents() 获取完整继承链,再对每个类调用 AnnotationCollector::getMethodsByClass(),最后按方法名合并(子类优先)。
示例逻辑:
$class = 'App\Controller\SubController';
$methods = AnnotationCollector::getMethodsByClass($class);
$inheritanceChain = array_merge([$class], class_parents($class));
foreach ($inheritanceChain as $parent) {
$parentMethods = AnnotationCollector::getMethodsByClass($parent);
foreach ($parentMethods as $methodName => $annotations) {
if (!isset($methods[$methodName])) {
$methods[$methodName] = $annotations;
}
}
}
注意点:
- 必须控制合并顺序:先子类,再逐级向上,否则父类注解会覆盖子类已定义的
- 不能用
get_declared_classes()或全量扫描——性能差且不可控 - trait 不在继承链中,
class_uses_recursive()拿到的是 trait 名,但无法获取其方法注解(反射层面无上下文)
多级继承下自定义注解的 target 必须精确匹配
如果自定义注解(如 Permission)同时支持 Target::CLASS 和 Target::METHOD,在多级继承场景中容易误用。
例如你在基类上写 @Permission("admin")(target 是 CLASS),又在子类方法上写 @Permission("user:list")(target 是 METHOD),getMethodsByClass() 只能拿到后者,前者根本不会出现在方法级结果里。
所以:
- 类级注解和方法级注解是两个独立维度,
getMethodsByClass()不会把类级注解“注入”到每个方法中 - 想实现「类级默认权限 + 方法级覆盖」,得自己解析
AnnotationCollector::getClassesByAnnotation(Permission::class),再结合方法注解做逻辑合并 - 别依赖注解的「继承语义」——PHP 没这机制,Hyperf 更没有
Hyperf 3.0 Attributes 下继承行为更严格
升级到 Hyperf 3.0 后,若改用 PHP 8 Attributes(如 #[Controller]、#[GetMapping]),继承规则反而更保守:Attribute 默认不继承(#[Attribute(Attribute::IS_REPEATABLE | Attribute::TARGET_METHOD)] 中不加 Attribute::INHERITED 就不会被子类继承)。
也就是说,即使你手动加了 Attribute::INHERITED,ReflectionMethod::getAttributes() 仍只返回当前类直接声明的 Attribute,不会自动合并父类的。Hyperf 的扫描器也是基于此反射 API 构建的。
所以:
- 不要假设
#[Permission]能跨类继承生效 - Hyperf 3.0 的
annotation-compat兼容层也不会改变这个底层行为 - 真正要复用逻辑,应该提取成公共服务或 AOP 切面,而不是靠注解“透传”
最易被忽略的一点:注解合并不是框架职责,而是业务逻辑的一部分。Hyperf 提供的是元数据快照,不是运行时代理。你看到的 getMethodsByClass() 返回结果,就是那一刻 AST 解析出的静态事实,不多也不少。











