Hyperf 的#[GetMapping], #[PostMapping] 等必须直接标注在 public 方法上。

Hyperf 的#[GetMapping], #[PostMapping] 等必须直接标注在 public 方法上。 它的本质是**Hyperf 的路由扫描器 (RouterScanner) 在启动阶段通过反射 (Reflection)遍历所有被Controller标记的类。它只查找Public 方法上的路由注解因为可访问性 (Accessibility)HTTP 请求是外部输入只能调用类的公开接口 (Public API)。Protected/Private 方法无法被外部直接触发。扫描策略 (Scanning Strategy)为了性能和安全扫描器默认忽略非 Public 方法避免将内部辅助逻辑意外暴露为 API 端点。元数据绑定 (Metadata Binding)路由信息URL、Method、Middleware是与特定方法签名绑定的。如果注解不在方法上扫描器无法建立 URL 到代码执行入口的映射。如果把 Controller 比作一家餐厅的菜单Controller 类是厨房。Public 方法是前台服务员能点的菜如“宫保鸡丁”。Private/Protected 方法是厨师内部的备菜步骤如“切葱”、“热油”。客人不能直接点“切葱”。#[GetMapping]是菜单上的条目。规则菜单条目必须对应一道可上桌的菜 (Public Method)。错误如果你把菜单条目写在“切葱”这个内部步骤上服务员路由器找不到这道菜客人客户端也就点不了。核心逻辑别把菜单贴在厨房的后门上。菜单必须贴在前台且对应的必须是能端出来的菜。一、扫描机制Hyperf 如何发现路由1. 启动时扫描 (Startup Scanning)组件hyperf/http-server中的RouterFactory和AnnotationScanner。流程找到所有带有Controller注解的类。使用ReflectionClass获取类的所有方法。过滤只保留isPublic()的方法。提取检查每个 Public 方法的 DocBlock 或 Attributes查找GetMapping,PostMapping等注解。注册将注解中的路径、HTTP 方法、中间件等信息注册到 Swoole/Hyperf 的路由表中。2. 为什么只扫描 Public安全性防止开发者误将内部逻辑如private function calculateTax()暴露为 HTTP 接口。规范性RESTful API 强调资源的操作这些操作应当是公开的契约。性能减少反射开销忽略大量私有/保护方法。 核心洞察路由注解是“对外承诺”。只有 Public 方法才能履行对外的承诺。二、可见性约束为什么 Private/Protected 不行1. 物理不可达 (Physically Unreachable)机制当 HTTP 请求到来时Hyperf 的核心调度器会执行类似$controllerInstance-$methodName()的操作。限制PHP 引擎禁止从外部上下文调用 Private/Protected 方法。结果即使你强行让扫描器识别了私有方法的路由运行时也会抛出Fatal Error: Call to private method。2. 语义冲突 (Semantic Conflict)Private/Protected意为“内部实现细节”可能随时变更不保证稳定性。Routing意为“公开 API 端点”需要保持稳定供客户端调用。冲突将不稳定内部细节标记为稳定公开端点是架构设计的反模式。3. 注解继承问题现象如果父类有GetMapping子类重写该方法但未加注解路由是否生效Hyperf 行为通常注解不自动继承到重写方法上除非显式声明。建议在每个具体的 Public 实现上明确标注注解确保路由清晰。三、常见错误与陷阱1. 错误注解写在 Private 方法上classUserController{/** * GetMapping(path/user/list) // ❌ 不会被扫描路由不存在 */privatefunctionlistUsers(){// ...}}后果启动时无报错取决于配置但访问/user/list返回404 Not Found。调试运行php bin/hyperf.php describe:routes查看已注册路由确认该路径缺失。2. 错误注解写在类级别而非方法级别/** * Controller(prefix/user) * GetMapping(path/list) // ❌ 无效GetMapping 必须作用于方法 */classUserController{publicfunctionlist(){...}}后果路由未注册。修正Controller定义前缀GetMapping定义具体路径和方法。3. 错误方法不是 PublicclassUserController{#[GetMapping(path/list)]protectedfunctionlist(){// ❌ 扫描器忽略 Protected// ...}}后果404。修正改为public function list()。4. 错误路径冲突现象两个不同的 Public 方法使用了相同的路径和 HTTP 方法。后果后扫描到的路由覆盖先前的或者启动报错取决于版本配置。对策确保路由路径唯一性。四、最佳实践如何正确标注1. 标准写法useHyperf\HttpServer\Annotation\Controller;useHyperf\HttpServer\Annotation\GetMapping;useHyperf\HttpServer\Annotation\PostMapping;#[Controller(prefix:/api/user)]classUserController{// ✅ 正确Public 方法 方法级注解#[GetMapping(path:/list)]publicfunctionlist(){return[users[]];}// ✅ 正确支持参数绑定#[PostMapping(path:/create)]publicfunctioncreate(RequestInterface$request){$data$request-all();// ...}// ❌ 内部逻辑不加路由注解保持 PrivateprivatefunctionvalidateData(array$data):bool{returntrue;}}2. 使用describe:routes验证命令php bin/hyperf.php describe:routes作用列出所有已注册的路由、方法、回调函数。价值开发过程中每次添加路由后运行此命令确认路由是否生效路径是否正确。3. 分组与中间件场景一组路由需要相同的中间件如 Auth。做法在Controller或方法注解中指定middleware。#[GetMapping(path:/profile,middleware:[AuthMiddleware::class])]publicfunctionprofile(){...}4. 属性注解 (Attributes) vs 文档注释 (DocBlock)趋势PHP 8 推荐使用#[Attribute]语法。优势语法更严格IDE 支持更好不会被误删。兼容Hyperf 同时支持两种写法但建议统一使用 Attributes。 总结原子化“路由注解位置”全景图维度关键点本质公开 API 契约与内部实现的隔离扫描规则仅扫描 Public 方法上的路由注解底层原因外部可达性、安全性、反射性能常见错误注解在 Private/Protected 方法、404、路径冲突验证工具php bin/hyperf.php describe:routesPHP 隐喻Menu Items must point to Servable Dishes (Public Methods)公式Route_Registration Scan(Public_Methods) × Extract(Annotations)终极心法路由注解的本质是“对外服务的窗口”。只有打开的窗户 (Public)才能接收阳光 (Request)。别把窗户开在墙壁夹层里 (Private)。于可见中见契约于公开见服务以规范为尺解隐蔽之牛于 API 设计中求清晰之真。行动指令检查路由运行describe:routes确认所有预期的 API 都在列表中。审计可见性搜索项目中带有路由注解但非 Public 的方法修正为 Public 或移除注解。统一风格确保团队统一使用#[Attribute]或 DocBlock不要混用。思维升级记住每一个 Public 且带路由注解的方法都是你对世界做出的承诺。请慎重对待。