注解路由
注解路由利用 PHP 8 属性(Attribute)在控制器类与方法上直接声明路由规则,路由定义与业务代码同处一处。框架启动时自动扫描 app/Controller 目录(含子目录),将带有类级控制器注解的类解析为路由组。
三种注解
| 注解 | 作用域 | 说明 |
|---|---|---|
#[Controller] | 类 | 声明控制器路由组,仅注册带有 #[RouteMapping] 的方法 |
#[AutoController] | 类 | 继承 Controller,自动注册该类全部 public 方法为路由 |
#[RouteMapping] | 方法 | 定义单条路由及其文档元数据 |
三个注解的命名空间均为 Viswoole\Router\Annotation:
use Viswoole\Router\Annotation\{Controller, AutoController, RouteMapping};WARNING
框架只扫描带有类级 #[Controller] 或 #[AutoController] 注解的类;方法上的 #[RouteMapping] 必须与类级注解配合使用。
Controller 注解
#[Controller] 将控制器类注册为路由组(Group),类内方法的路由继承组的公共配置,只有标注了 #[RouteMapping] 的方法才会注册为路由:
<?php
namespace App\Controller;
use App\Middleware\AuthMiddleware;
use Viswoole\Router\Annotation\{Controller, RouteMapping};
#[Controller(prefix: 'user', title: '用户管理', middlewares: [AuthMiddleware::class])]
class UserController
{
// 路由: GET /user/info(未写 paths 时默认取方法名)
#[RouteMapping(method: 'GET', title: '用户信息')]
public function info(): array
{
return ['id' => 1, 'name' => 'Viswoole'];
}
// 普通方法:没有 RouteMapping,不会注册路由
public function helper(): void {}
}AutoController 注解
#[AutoController] 自动把类中全部 public 方法注册为路由(构造方法、抽象方法与析构方法除外),方法名即路径后段,无需逐个标注。未标注 #[RouteMapping] 的方法使用默认请求方法(继承全局 router.method,默认 '*' 不限制),需要收紧方法或自定义路径时,在该方法上追加 #[RouteMapping] 即可:
use Viswoole\Router\Annotation\AutoController;
#[AutoController(prefix: 'admin')]
class AdminController
{
// 路由: /admin/dashboard(默认继承 router.method,不限请求方法)
public function dashboard(): string
{
return 'Dashboard';
}
// 方法级注解覆盖默认配置:路由 GET /admin/settings
#[RouteMapping(method: 'GET')]
public function settings(): array
{
return [];
}
}需要更多自定义(文档信息、中间件等),可在其上标注 #[RouteMapping],方法级注解优先生效。
参数全表
#[Controller] 与 #[AutoController] 共享下表中除 prefix 语义外的全部参数(AutoController 完整继承 Controller 的构造签名);#[RouteMapping] 以 paths 承担同样的路径语义,并额外支持文档维护参数。
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
prefix / paths | string|array|null | null | 路径前缀(类级)/ 路径列表(方法级);null 时类级取类短名、方法级取方法名;支持多个路径映射同一方法 |
id | string|null | null | 路由唯一标识;null 时自动生成(类级取类完全限定名哈希,方法级取 类::方法 哈希) |
parentId | string|null | null | 父级分组 id,必须是已注册的分组路由 id,用于把路由挂到指定分组 |
method | string|string[]|null | null | 允许的 HTTP 方法;null 时继承全局 router.method 配置(默认 '*' 不限制) |
middlewares | array|null | null | 中间件列表,支持类名、[类名, 构造参数数组];注解参数必须是常量表达式,不支持闭包 |
patterns | array<string,string>|null | null | 动态路由变量正则约束,键为变量名 |
meta | array|null | null | 自定义元数据(关联数组) |
suffix | string|array|null | null | 伪静态后缀,覆盖全局配置 |
domain | string|array|null | null | 生效域名,覆盖全局配置 |
hidden | bool | false | 是否在 API 文档中隐藏 |
title | string|null | null | 标题;未声明时从 PHPDoc 提取 |
description | string|null | null | 描述;未声明时从 PHPDoc 提取 |
sort | int | 0 | 排序,数值越大越靠前 |
#[RouteMapping] 额外支持的文档维护参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
author | string | '' | 接口作者 |
createdAt | string | '' | 创建时间 |
updatedAt | string | '' | 更新时间 |
tags | string[] | [] | 接口标签列表 |
status | Status | Status::DEVELOPMENT | 接口状态,见 Status 枚举 |
路径合并规则
方法路径与类前缀(或父分组前缀)的合并规则由路径是否以 / 开头决定:
| 方法路径写法 | 语义 | 合并结果(类前缀 user) |
|---|---|---|
info | 相对路径 | /user/info(拼接前缀) |
/health | 绝对路径 | /health(忽略类前缀) |
/ | 类前缀本身 | /user(组默认入口) |
#[Controller(prefix: 'api/v1')]
class HomeController
{
// 路由: GET /api/v1/index —— 相对路径,拼接类前缀
#[RouteMapping(method: 'GET')]
public function index(): array { return []; }
// 路由: GET /health —— 绝对路径,脱离类前缀
#[RouteMapping(paths: '/health', method: 'GET')]
public function health(): array { return ['ok' => true]; }
// 路由: GET /api/v1 —— 单独的 / 表示控制器组入口
#[RouteMapping(paths: '/', method: 'GET')]
public function home(): array { return []; }
}该规则与编程式路由分组完全一致;顶层注册的路径不以 / 开头时自动补全为 /。
动态参数
方法路径中可使用 {参数名}(必选)与 {参数名?}(可选,缺失时由处理器参数默认值兜底),通过 patterns 约束格式:
#[RouteMapping(
paths: ['article/{category}/{id}'],
method: 'GET',
patterns: ['category' => '[a-z]+', 'id' => '\d+'],
title: '文章详情',
)]
public function detail(string $category, int $id): array
{
// GET /article/php/42 → $category = 'php', $id = 42
return compact('category', 'id');
}变量名必须以字母或下划线开头、仅含字母数字下划线且不超过 32 字符,同一路径内不可重复;未约束的变量使用全局 router.default_pattern_regex 校验。动态参数会注入到同名的处理器方法参数中。
从 PHPDoc 提取标题与描述
注解未声明 title / description 时,框架自动从 PHPDoc 提取:首个空行之前的内容为标题,之后到首个 @ 标签之间的正文为描述:
#[Controller(prefix: 'order')]
class OrderController
{
/**
* 订单列表。
* 分页查询当前登录用户的订单记录,
* 支持按状态筛选。
*/
#[RouteMapping(method: 'GET')]
public function list(): array
{
// title = "订单列表。"(首行/空行前)
// description = "分页查询当前登录用户的订单记录,支持按状态筛选。"
return [];
}
}类级 #[Controller] 未声明 title 时同样从类 PHPDoc 首段提取。推荐以注解参数显式声明,PHPDoc 提取作为兜底。
Status 枚举
status 参数使用 Viswoole\Router\ApiDoc\Status 枚举(Backed Enum),默认 Status::DEVELOPMENT。API 文档输出时附带中文标签与展示颜色:
| 枚举项 | value | 中文标签 | 颜色 |
|---|---|---|---|
Status::PUBLISHED | published | 已发布 | #28a745 绿色 |
Status::DEVELOPMENT | development | 开发中 | #17a2b8 青色 |
Status::DEPRECATED | deprecated | 已废弃 | #6c757d 灰色 |
Status::ERROR | error | 有异常 | #dc3545 红色 |
Status::TO_BE_DEPRECATED | to_be_deprecated | 将废弃 | #e9ecef 浅灰色 |
Status::TESTING | testing | 测试中 | #ffc107 黄色 |
use Viswoole\Router\ApiDoc\Status;
#[RouteMapping(method: 'POST', title: '创建用户', status: Status::PUBLISHED)]
public function create(): array { return []; }配置合并优先级
方法级 #[RouteMapping] 与类级注解按以下规则合并:
- paths:按上文路径合并规则与类级
prefix拼接 - middlewares:方法级追加到类级中间件之后
- patterns:按变量名合并,方法级同名约束覆盖类级
- method / domain / suffix / 其他:方法级显式声明时覆盖类级,否则继承
