API 文档生成
路由系统内置 API 文档生成能力:开启 router.api_doc.enable 后,框架在路由注册阶段解析处理器签名中的注入注解、PHPDoc 与返回值注解,构建结构化的接口文档(参数结构 + 响应结构),并可编程查询。
启用与配置
// config/router.php
'api_doc' => [
// 是否启用
'enable' => true,
// 全局返回值声明,值为 Viswoole\Router\ApiDoc\Annotation\Returned 实例数组
'returned' => [],
// 全局请求头参数,格式见下文
'header' => [],
// 全局查询参数(GET),格式同 header
'query' => [],
// 全局请求体参数(POST),格式同 header
'body' => [],
],| 配置键 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enable | bool | false | 是否启用 API 文档生成 |
returned | array | [] | 全局返回值声明,必须是 Returned 实例数组 |
header | array | [] | 全局请求头参数 |
query | array | [] | 全局查询参数 |
body | array | [] | 全局请求体参数 |
全局参数(header/query/body)支持三种格式,启动时统一归一化为 FieldStructure 实例:
use Viswoole\Router\ApiDoc\Structure\{FieldStructure, Types};
'header' => [
// 格式一:FieldStructure 实例(可精确控制类型)
new FieldStructure('authorization', '鉴权令牌', type: Types::String),
// 格式二:极简格式,'参数名' => '描述'(类型默认 string)
'x-app-id' => '应用 ID',
// 格式三:关联数组
['name' => 'x-sign', 'description' => '签名', 'type' => 'string'],
],其中数组格式的 type 支持 Types 枚举或类型字符串(string/int/float/bool/array/object/null,其他值按 mixed 处理);FieldStructure 的完整构造参数为 name、description、allowNull、default、type。
请求参数声明
参数来源由 Inject 注解标记
参数出现在文档的哪个分组,由参数上的自动注入注解决定(注解命名空间 Viswoole\HttpServer\AutoInject,无构造参数):
| 注解 | 参数来源 | 文档分组 |
|---|---|---|
#[InjectGet] | GET 查询参数 | query |
#[InjectPost] | POST 请求体 | body |
#[InjectHeader] | 请求头 | header |
#[InjectFile] | 上传文件 | body(类型标记为 file) |
use Viswoole\HttpServer\AutoInject\{InjectGet, InjectPost, InjectHeader, InjectFile};
use Viswoole\HttpServer\Message\UploadedFile;
use Viswoole\Router\Annotation\{Controller, RouteMapping};
#[Controller(prefix: 'user')]
class UserController
{
/**
* 更新头像
*/
#[RouteMapping(method: 'POST', title: '更新头像')]
public function avatar(
#[InjectHeader] string $authorization, // header 参数
#[InjectGet] int $from = 1, // query 参数(有默认值,可选)
#[InjectFile] UploadedFile $file, // file 参数(单文件)
): array { return []; }
}未标注来源注解的处理器参数不会进入文档。文件参数:类型含 array 时视为多文件(UploadedFile[]),否则视为单文件。
参数描述与类型
- 描述取自方法 PHPDoc 中
@param标签的描述部分 - 类型优先取
@param标签的 PHPStan 风格类型声明(可表达反射无法描述的结构),解析失败时回退到参数反射类型
/**
* 创建订单
*
* @param int $userId 用户ID
* @param array{id: int, name?: string, tags: string[]} $items 商品列表
*/
#[RouteMapping(method: 'POST', title: '创建订单')]
public function create(
#[InjectPost] int $userId,
#[InjectPost] array $items,
): array {}支持的 docblock 类型语法:
| 语法 | 说明 | 示例 |
|---|---|---|
| 基础类型 | int / string / bool / float 等 | @param string $name |
| 数组后缀 | 元素类型加 [],支持多维 | string[]、int[][] |
| 联合类型 | | 分隔,可括号分组 | int|string、(int|string)[] |
| 关联结构体 | array{key: type},键名 ? 后缀标记可选字段,支持嵌套 | array{id: int, name?: string} |
| 类 / 枚举 | 完全限定名或全局命名空间短名(不解析 use 别名) | \App\Enum\Suit |
参数是否可空由参数类型是否允许 null(含默认值 null)决定;参数默认值会写入文档。
Returned 注解(返回值)
#[Returned] 声明接口的响应结构,标注在处理器方法上,可重复标注(每种响应一个注解)。命名空间 Viswoole\Router\ApiDoc\Annotation:
use Viswoole\Router\ApiDoc\Annotation\Returned;
#[Returned('成功', ['id' => '用户ID', 'name?' => '姓名,可选', 'roles' => [['id' => '角色ID']]], 200)]
#[Returned('创建成功', ['code' => '状态码'], 201)]
public function create(): array {}属性说明
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
title | string | 必填 | 该响应的标题 |
data | array|string | 必填 | 示例响应数据,自动推导结构 |
statusCode | int | 200 | HTTP 状态码 |
type | string | application/json | 响应内容类型,内置常量 TYPE_JSON / TYPE_XML / TYPE_HTML / TYPE_TEXT / TYPE_STREAM |
sort | int | 0 | 排序,数值越大越靠前 |
data 结构推导规则
data 传入数组时,框架按示例值递归推导字段类型:
- 键名语法:
'字段|描述'携带字段描述,'字段?|描述'标记可选字段(允许null) - 关联数组:视为对象结构,逐字段解析;索引数组:视为数组结构(
Array<元素类型>) - 嵌套数组:递归解析,如
'roles' => [['id' => '角色ID']]推导为对象数组 - 枚举值:Backed 枚举取 backing value(
int/string),纯枚举取枚举项名(string) - 对象:解析其公共属性生成结构
- 字符串:整个
data为字符串时按string类型处理
#[Returned('成功', [
'code' => 200, // int
'message' => '成功', // string
'data|访问令牌' => [ // 嵌套对象,带描述
'token' => 'eyJ0eXAi...', // string
'expires_in|有效期(秒)' => 3600, // int,带描述
],
'avatar?' => null, // 可选字段,null 类型
])]
public function login(): array {}全局参数排除(IgnoreGlobal)
配置全局参数后,个别接口可能不需要其中部分字段。#[IgnoreGlobal](命名空间 Viswoole\Router\ApiDoc\Annotation,可重复标注)可排除全局配置的生效范围,支持标注在控制器类(对类内全部路由生效)或方法上,两类规则叠加:
use Viswoole\Router\ApiDoc\Annotation\IgnoreGlobal;
#[IgnoreGlobal] // 排除全部全局配置(header+query+body+returned)
#[IgnoreGlobal('header')] // 仅排除全局请求头
#[IgnoreGlobal(['header', 'query'])] // 排除全局请求头与查询参数
#[IgnoreGlobal(name: 'authorization')] // 任意来源中名为 authorization 的全局字段
#[IgnoreGlobal('header', 'authorization')] // 精确排除全局 header 中的 authorization 字段
public function login(): array {}| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
source | string|array|null | null | 排除的来源,有效值 header / query / body / returned;null 表示全部;传非法值会抛出异常 |
name | string|array|null | null | 排除的字段名;null 表示该来源全部字段;returned 来源时按返回声明的标题匹配 |
INFO
方法上通过 #[InjectHeader] 等注解声明的局部参数不受 #[IgnoreGlobal] 影响,被排除的接口仍可按需声明个别参数。
类型与 TypeScript 的对应
文档输出的类型以 TypeScript(TS)兼容为设计目标,由文档渲染端完成映射:
| 框架类型 | TS 类型 |
|---|---|
int / float | number |
bool | boolean |
mixed | any |
string / array / object | string / Array<T> / object |
SUCCESS
类型越具体文档越有价值:编写 Returned 示例数据与 @param 类型时,优先给出 int/string 等具体类型或 array{id: int} 结构体,避免笼统的 mixed、object。
查询 API 文档
文档数据通过 Router 门面编程获取,可用于自建文档站点或调试:
use Viswoole\Router\Facade\Router;
// 获取全部接口文档列表(hidden 为 true 的分组及分组内路由不会进入列表)
$list = Router::getApiList(); // ['count' => N, 'routes' => [...]]
// 获取指定接口的参数与返回值详情(citeLink 取自列表项)
$detail = Router::getApiDetail($citeLink); // ['params' => [...], 'returned' => [...]]列表项为分组(type: "group")与路由(type: "route")组成的树,路由节点包含 id、parentId、citeLink、title、description、paths、methods、domains、suffix、tags、author、createdAt、updatedAt、meta、source(源码位置)与 status(含 value/中文 label/展示 color):
{
"type": "route",
"id": "路由ID",
"parentId": "分组ID",
"citeLink": "分组ID.路由ID",
"title": "创建订单",
"paths": ["/api/v1/order/create"],
"methods": ["POST"],
"tags": [],
"status": { "value": "development", "label": "开发中", "color": "#17a2b8" }
}getApiDetail($citeLink) 返回的 params 按 body / header / query 三个来源分组,每项为 FieldStructure 结构(字段名、描述、是否可空、默认值、类型);returned 为按 sort 排序的响应声明列表。
WARNING
确认 router.api_doc.enable 已设为 true——该功能关闭时 Route 实例不会解析参数与返回值注解,getApiList() 始终返回空列表。
