easyswoole / http-annotation
php stander lib
Requires
- php: >=8.1
- ext-dom: *
- ext-json: *
- ext-libxml: *
- ext-mbstring: *
- ext-simplexml: *
- easyswoole/http: 3.x
- easyswoole/parsedown: ^1.0
- psr/http-message: ^1.0
Requires (Dev)
- easyswoole/swoole-ide-helper: ^1.0
- phpunit/phpunit: ^13.4
Suggests
None
Provides
None
Conflicts
None
Replaces
None
- 4.x-dev
- 4.1.5
- 4.1.4
- 4.1.3
- 4.1.2
- 4.1.1
- 4.0.11
- 4.0.10
- 4.0.9
- 4.0.8
- 4.0.7
- 4.0.6
- 4.0.5
- 4.0.4
- 4.0.3
- 4.0.2
- 4.0.1
- 3.x-dev
- 3.3.4
- 3.3.3
- 3.3.2
- 3.3.1
- 3.2.22
- 3.2.21
- 3.2.20
- 3.2.19
- 3.2.18
- 3.2.17
- 3.2.16
- 3.2.15
- 3.2.14
- 3.2.13
- 3.2.12
- 3.2.11
- 3.2.10
- 3.2.9
- 3.2.8
- 3.2.7
- 3.2.6
- 3.2.5
- 3.2.3
- 3.2.2
- 3.2.1
- 3.1.10
- 3.1.9
- 3.1.8
- 3.1.6
- 3.1.5
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.9
- 3.0.7
- 3.0.6
- 3.0.5
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 2.x-dev
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.0
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.5.0
- 1.4.6
- 1.4.5
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.1
- 1.3.0
- 1.2.6
- 1.2.5
- 1.2.4
- 1.2.3
- 1.2.2
- 1.2.1
- 1.2.0
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.15
- 1.0.14
- 1.0.13
- 1.0.12
- 1.0.11
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.1
- 1.0.0
- dev-master
This package is auto-updated.
Last update: 2026-10-10 17:11:52 UTC
README
EasySwoole HTTP 控制器的 PHP 原生属性组件。通过 #[Api]、Param 等定义请求方法、参数来源、类型转换、校验规则及接口说明,并生成支持在线试运行的独立 HTML 文档。
安装与环境
composer require easyswoole/http-annotation
需要 PHP 8.1 或以上,以及 JSON、mbstring、DOM、SimpleXML、libxml 扩展。HTTP 处理依赖 easyswoole/http 3.x;运行下面的 Swoole 服务示例还需要实际安装 Swoole 扩展,IDE helper 不提供运行能力。
定义控制器
控制器继承 AnnotationController。ApiGroup 定义文档分组,公开的非静态方法通过 Api 定义接口。
例如将下面的控制器保存为 App/HttpController/Common/Message.php,并在业务项目的 Composer 中配置 App\ 自动加载。
<?php namespace App\HttpController\Common; use EasySwoole\HttpAnnotation\AnnotationController; use EasySwoole\HttpAnnotation\Attributes\Api; use EasySwoole\HttpAnnotation\Attributes\ApiGroup; use EasySwoole\HttpAnnotation\Attributes\Param; use EasySwoole\HttpAnnotation\Enum\ContentType; use EasySwoole\HttpAnnotation\Enum\HttpMethod; use EasySwoole\HttpAnnotation\Enum\ParamFrom; use EasySwoole\HttpAnnotation\Enum\ParamType; use EasySwoole\HttpAnnotation\Validator\IsFile; use EasySwoole\HttpAnnotation\Validator\NotEmpty; use EasySwoole\HttpAnnotation\Validator\Required; #[ApiGroup(groupName: 'Common.Message', description: '消息接口')] class Message extends AnnotationController { #[Api( allowMethod: HttpMethod::GET, requestParam: [ new Param(name: 'page', from: ParamFrom::GET, type: ParamType::INT, value: 1), ], description: '分页读取消息' )] public function list(int $page) { $this->writeJson(200, ['page' => $page]); } #[Api( allowMethod: HttpMethod::POST, acceptContentType: ContentType::JSON, requestParam: [ new Param( name: 'msgId', from: ParamFrom::JSON, type: ParamType::STRING, validate: [new Required(), new NotEmpty()] ), new Param(name: 'testHeader', from: ParamFrom::HEADER), ], description: '根据消息 ID 读取详情' )] public function detail(string $msgId, string $testHeader) { $this->writeJson(200, ['msgId' => $msgId, 'testHeader' => $testHeader]); } #[Api( allowMethod: HttpMethod::POST, acceptContentType: ContentType::FORM_DATA, requestParam: [ new Param( name: 'userThumb', from: ParamFrom::FILE, type: ParamType::FILE, validate: [new Required(), new IsFile()] ), ] )] public function update(array $data) { // $data['userThumb'] 是上传文件对象,由业务代码处理保存。 $this->writeJson(200, ['received' => isset($data['userThumb'])]); } }
普通方法的形参名称需要与属性中定义的参数名称一致。方法只有一个 array 形参时,组件将接口的 requestParam 打包成数组传入。此数组模式只收集接口参数;公共参数可在 onRequest 中接收。
请求方法、Content-Type 与参数来源
Api::allowMethod 为单个 HttpMethod 枚举,默认 GET,不接受方法数组。
构造函数检查接口定义的一致性,不合规时抛出 Exception\Annotation。文档扫描或首次解析控制器属性时也会触发检查。
| 请求方法 / Content-Type | 允许的请求体参数来源 |
|---|---|
GET、HEAD:acceptContentType 必须为 null |
不允许 POST、JSON、XML、RAW_POST、FILE |
| FORM_DATA | POST、FILE |
| FORM_URLENCODED | POST |
| JSON | JSON |
| XML | XML |
| RAW | RAW_POST |
GET、HEAD 的 Content-Type 默认是 null;其他方法未指定时默认为 FORM_DATA。GET、HEADER、COOKIE、DI、CONTEXT 不属于请求体来源,可与上表中的内容类型一起定义。
特别注意:
Param::from默认是[ParamFrom::GET]。GET、HEAD 接口必须显式指定合规来源,例如from: ParamFrom::GET;JSON、XML、RAW 接口也应显式指定匹配的来源。from可以是单个枚举或数组。数组不能为空,每一项都必须合规;不能把不合规来源当作“备用来源”。读取时按数组顺序选择第一个命中的来源。requestParam必须由Param对象组成,同一接口内不允许重复参数名称。- 这里校验的是 Api 定义。当前控制器运行时会检查 HTTP 方法,但没有单独核验实际请求的
Content-Type是否与acceptContentType一致。需要严格限制实际请求头时,应在业务层增加检查。 onRequest中的公共Param不经过Api构造函数这项来源校验,公共参数也应根据使用它的接口合理定义。
Param 常用配置
| 属性 | 用途与默认值 |
|---|---|
name |
参数名,用于取值和方法形参匹配 |
from |
参数来源数组,默认 [ParamFrom::GET],兼容单个枚举 |
type |
默认 ParamType::STRING;设置为 null 可保留原值 |
value |
未取到参数时的默认值,默认 null |
validate |
校验器对象数组,默认空数组 |
description |
普通字符串或 Text;参数说明不接受 Markdown |
deprecated |
标记废弃,文档显示“已废弃”,不会禁止调用 |
ignoreAction |
忽略该参数的 action 名称列表 |
ignorePassArgWhenNotSet |
单数组形参模式中,未传入时不加入参数数组 |
完整的验证器参数、规则行为和使用示例见 验证器使用指南。
IsUrl(allowProtocols: ['http', 'https']) 可限制 URL 协议;IsIp(mode: 'IPV4') 或 IsIp(mode: 'IPV6') 可限制 IP 版本。这两个规则的校验配置参数位于首位,errorMsg 位于最后;默认分别不额外限制协议、允许 IPv4/IPv6。
支持 STRING、INT、DOUBLE、REAL、FLOAT、BOOLEAN、FILE、NULL_WHILE_EMPTY 类型。转换发生在校验前,转换本身不是合法性校验,必要时仍需配置校验器。
未传参数与空值转换
ignorePassArgWhenNotSet: true 只在 action 使用单个 array 形参接收参数时生效:没有取到参数(hasSet() === false)时,不将该参数加入传给 action 的数组。独立形参模式不应用此选项。它不跳过参数校验,因此配置 Required 等规则时,未传参数仍可能校验失败。
GET、POST、JSON 字段通过 isset 判断是否存在,未传字段和显式传入 null 均视为未设置;空字符串 ''、整数 0、字符串 '0'、false 则视为已设置,仍会传入。声明了默认 value 也不会改变 hasSet(),未设置时仍会省略该参数。
type: ParamType::NULL_WHILE_EMPTY 使用 PHP 的 empty() 规则转换值,但特别保留整数 0 和字符串 '0':
| 原始值 | 转换后的值 |
|---|---|
null、''、false、[]、浮点 0.0 |
null |
整数 0、字符串 '0' |
保留原值和类型 |
空格字符串 ' '、其他非空值 |
保留原值和类型 |
转换不会改变是否已设置参数。组合使用这两个选项时,已传入的空字符串会转为 null,但不会从参数数组中省略。
例如,定义一个可选的请求限制周期参数,action 使用数组接收参数:
use EasySwoole\HttpAnnotation\Attributes\Api; use EasySwoole\HttpAnnotation\Attributes\Param; use EasySwoole\HttpAnnotation\Enum\ParamFrom; use EasySwoole\HttpAnnotation\Enum\ParamType; use EasySwoole\HttpAnnotation\Validator\Optional; #[Api(requestParam: [ new Param( name: 'queryLimitPeriod', from: [ParamFrom::GET], validate: [new Optional()], description: '请求限制周期,单位秒', type: ParamType::NULL_WHILE_EMPTY, ignorePassArgWhenNotSet: true, ), ])] public function updateLimit(array $params): void { // 未传参数:$params 为 []。 // 传入 queryLimitPeriod='':$params 为 ['queryLimitPeriod' => null]。 }
下面对比上述配置中 ignorePassArgWhenNotSet 开启和关闭时的结果。默认值为 null;GET、POST、JSON 字段的是否设置判断规则相同,表格假设参数校验通过:
请求中的 queryLimitPeriod |
hasSet() |
解析后的值 | ignorePassArgWhenNotSet: true 的 action 数组 |
ignorePassArgWhenNotSet: false 的 action 数组 |
|---|---|---|---|---|
| 未传入 | false |
null(不做类型转换) |
[],不包含该字段 |
['queryLimitPeriod' => null] |
显式传入 null |
false |
null(不做类型转换) |
[],不包含该字段 |
['queryLimitPeriod' => null] |
空字符串 '' |
true |
null |
['queryLimitPeriod' => null] |
['queryLimitPeriod' => null] |
false、空数组 []、浮点数 0.0 |
true |
null |
['queryLimitPeriod' => null] |
['queryLimitPeriod' => null] |
整数 0 |
true |
整数 0 |
['queryLimitPeriod' => 0] |
['queryLimitPeriod' => 0] |
字符串 '0' |
true |
字符串 '0' |
['queryLimitPeriod' => '0'] |
['queryLimitPeriod' => '0'] |
空格字符串 ' ' |
true |
字符串 ' ' |
['queryLimitPeriod' => ' '] |
['queryLimitPeriod' => ' '] |
非空字符串 '30' |
true |
字符串 '30' |
['queryLimitPeriod' => '30'] |
['queryLimitPeriod' => '30'] |
ignorePassArgWhenNotSet 判断的是是否取到参数,不是解析结果是否为 null。NULL_WHILE_EMPTY 只转换空值,不将非空数字字符串转换为整数。
#[Api(requestParam: [
new Param(
name: 'remark',
from: [ParamFrom::GET],
type: ParamType::NULL_WHILE_EMPTY,
ignorePassArgWhenNotSet: true
),
])]
public function update(array $params): void
{
// 未传 remark 或传入 null:$params 中没有 remark。
// 传入 remark='':$params 为 ['remark' => null]。
// 传入 remark='0':$params 为 ['remark' => '0']。
}
- 上传文件同时配置
from: ParamFrom::FILE和type: ParamType::FILE,避免默认字符串转换影响文件对象。 - 未从声明来源取到参数时,不做类型转换,
value默认值原样保留;只有实际取到的值才按type转换。文档默认值列展示声明时的原值,保留0、false、空字符串,null显示为-。 Required检查是否设置参数,NotEmpty检查值是否为空,两者含义不同。Header 参数还应根据需要使用NotEmpty,缺失请求头不会标记为已设置。RequiredIf、RequiredWith、RequiredWithout支持条件必填;同一参数的Optional*(包括IgnoreValidatorWhenEmpty)与Required*、NotEmpty互斥,同时定义会直接抛出配置异常。- 想用
Optional保留“未传入且为 null”的语义时,应显式设置type: null,避免默认 STRING 将null转成空字符串。 - BOOLEAN 使用 PHP 布尔转换;字符串
"false"会被转成true。表单布尔值建议使用1、0。 - JSON 参数按字段名从请求体解码结果取值;XML 参数从根节点的直接子节点取值;RAW_POST 返回整个请求体。
内置校验器位于 src/Validator,包括必填、长度、数值、日期、邮箱、文件和字段比较等。控制器在公共参数和 action 参数校验阶段均传入已解析的 allDefineParams,支持跨字段比较及条件可选规则;action 同名参数优先,ignoreAction 排除的参数不会进入该集合。
公共参数与继承
在 onRequest 上使用可重复的 #[Param] 定义公共参数。接口定义的同名参数优先覆盖公共定义。
use EasySwoole\HttpAnnotation\AnnotationController; use EasySwoole\HttpAnnotation\Attributes\Param; use EasySwoole\HttpAnnotation\Enum\ParamFrom; use EasySwoole\HttpAnnotation\Validator\NotEmpty; abstract class BaseController extends AnnotationController { #[Param(name: 'token', from: ParamFrom::HEADER, validate: [new NotEmpty()])] public function onRequest(?string $action, ?array $data = null): ?bool { // 此处可读取 $data['token'],执行实际鉴权逻辑。 return true; } }
覆盖父类 onRequest 后,使用 #[ExtendParam] 合并父类的公共参数;使用 #[ExtendParam(parentParamsName: ['token'])] 只继承指定参数,子类同名定义优先。ExtendParam 仅对 onRequest 有效。
ignoreAction 可用于公共参数豁免,例如开放接口不要求 token。不要在单数组传参模式中对接口自身的参数使用 ignoreAction:当前数组收集分支仍会访问这些被移除的参数。
前置回调与属性注入
#[PreCall([Hooks::class, 'before'])] 可用于控制器类或接口方法,回调返回 false 时中止后续执行。
- 类级回调接收
($actionName, $request, $response),在参数解析前执行。 - 方法级回调接收
($request, $response),在参数解析、校验后执行。 #[Di(key: 'service')]和#[Context(key: 'user')]可给控制器公开或受保护属性注入值,同一属性不能同时定义这两种属性。注入发生在方法级前置回调之后。
参数解析和校验在调用父控制器 __hook() 之前执行,异常不进入父方法内部的 onException() 捕获块。接入项目时应核对上层分发器的异常处理,按业务要求返回错误响应。
接口说明与示例
ApiGroup::description 和 Api::description 支持普通字符串、Text、Markdown。文档全局说明使用 Config::setDescription(),传入 Text 或 Markdown 对象。
use EasySwoole\HttpAnnotation\Bean\Description\Markdown; use EasySwoole\HttpAnnotation\Bean\Description\Text; $markdown = new Markdown(__DIR__ . '/docs/message.md'); $text = new Text('消息接口说明'); $textFile = new Text(__DIR__ . '/docs/message.txt', isFile: true);
Markdown 的构造参数是 Markdown 文件路径,不是 Markdown 内容字符串。生成文档时使用 easyswoole/parsedown 转成 HTML;Text 按纯文本转义展示。说明文件必须存在,建议用 __DIR__ 拼接绝对路径。Markdown 渲染可能保留内嵌 HTML,说明文件应来自可信内容。
Api::requestExamples、responseExamples 接收示例对象数组:
use EasySwoole\HttpAnnotation\Bean\Example\Array2Json; use EasySwoole\HttpAnnotation\Bean\Example\ArrayForm; use EasySwoole\HttpAnnotation\Bean\Example\Raw; $requestExamples = [ new Array2Json(['msgId' => '123']), new ArrayForm(['page' => 1]), new Raw('<request><msgId>123</msgId></request>'), ]; $responseExamples = [ new Array2Json(['code' => 200, 'data' => []]), new Array2Json(['code' => 400, 'message' => '参数错误'], isSuccessResponse: false), ];
Raw 也支持 isFile: true。响应示例通过 isSuccessResponse 分到成功、失败区域,各类示例分别从 1 编号。Api::deprecated 只控制废弃标识,不禁止接口执行。
生成接口文档
<?php require __DIR__ . '/vendor/autoload.php'; use EasySwoole\HttpAnnotation\Bean\Description\Markdown; use EasySwoole\HttpAnnotation\Document\Document; $document = new Document( __DIR__ . '/App/HttpController', 'App\\HttpController' ); $document->getConfig()->setProjectName('消息服务'); $document->getConfig()->setHost('http://127.0.0.1:9501'); $document->getConfig()->setDescription(new Markdown(__DIR__ . '/docs/introduction.md')); file_put_contents(__DIR__ . '/api.html', $document->scan2html()); // 也可 echo $document->scan2html(),作为 HTTP 响应输出。
scanAllApiGroup()收集控制器属性;scan2ArrayMap()返回文档树;scan2html()返回完整 HTML 字符串,不自动保存文件。- 扫描的控制器需能被 Composer 自动加载、继承
AnnotationController且定义ApiGroup。仅带Api的公开非静态方法进入文档;每个文件目前只使用扫描到的第一个类。 - 分组名称不可重复。
Common.Message、Common.Profile生成 Common → Message / Profile → action 的侧边栏。没有接口且没有有效后代的节点不展示。 - 控制器命名空间参数必须与实际目录对应。文档路径由控制器类名和方法名生成,各路径层级首字母转小写,尾部 Index 控制器与 index action 有简写处理。
Api构造函数当前没有requestPath参数,不要使用requestPath: ...;registerRouter也尚未实现自动注册路由。文档路径应与业务路由配置一致。responseParam虽可定义,当前 HTML 模板尚未展示响应参数表,可通过响应示例说明返回结构。- 修改定义后应重新生成 HTML、刷新浏览器;常驻服务中的属性缓存还需要通过重启相关工作进程刷新。
“立即尝试”使用与限制
点击 action 标题旁的“立即尝试”,填写参数后运行。
- 请求方法固定使用
Api::allowMethod,不可在窗口内切换;地址可编辑,默认由配置 host 和接口路径组合。 - 表单类型显示参数输入框,预填
Param::value。勾选的参数才发送,编辑参数后会自动勾选;FILE 类型显示文件选择器,选中文件后自动勾选。 - JSON、XML、RAW 使用一个多行输入框提交完整请求体,JSON/XML 会检查格式。Header 和 URL 参数仍可单独填写。
- Header 来源的参数作为 HTTP 请求头发送;GET 来源作为 URL 查询参数发送。DI、CONTEXT 不由浏览器填写,Cookie 由浏览器管理。
- 所有 HTTP 状态都显示,包括 4xx、5xx;结果区展示状态码、耗时、响应内容及浏览器允许读取的响应头。JSON 响应会格式化,空响应也有提示。
- 超时可设置为 1–300 秒。网络异常、超时和取消会显示结果;点击关闭或在弹窗内按 Esc 会取消正在进行的请求。
- 打开弹窗时焦点移到关闭按钮,关闭后回到“立即尝试”。焦点在浏览器地址栏、开发者工具或其他窗口时,页面无法接收 Esc。
特别注意浏览器请求的实际限制:
- 建议通过 HTTP 服务访问生成文档。例如在仓库目录执行
php -S 127.0.0.1:8080,再打开http://127.0.0.1:8080/api.html。该服务只用于静态文档预览,业务接口仍需运行自己的服务。 - 请求使用
credentials: 'include'。跨域接口应允许文档的具体 Origin、凭据、请求方法和自定义 Header;需要凭据时不能用Access-Control-Allow-Origin: *。HTTPS 文档请求 HTTP 接口还可能被混合内容策略阻止。 - 浏览器禁止设置的请求头不能由表单强行发送;跨域响应头的可见范围由服务端 CORS 配置决定。连接失败或 CORS 拦截时通常无法获得 HTTP 状态码。
- RAW 试运行默认使用
text/plain。当前没有单独配置 RAW MIME 类型的界面。 - 当前普通表单在有文件时使用 multipart/form-data,无文件时使用 URL 编码表单;即使 Api 声明 FORM_DATA,也不会强制无文件表单使用 multipart。服务端严格限制内容类型时需要注意这一差异。
- HTML 是生成时的静态快照,示例数据、默认值和说明会写入文件,公开前检查是否包含不应公开的信息。
启动 Swoole HTTP 服务
<?php require __DIR__ . '/vendor/autoload.php'; use EasySwoole\Http\Dispatcher; use EasySwoole\Http\Request; use EasySwoole\Http\Response; use Swoole\Http\Server; $dispatcher = new Dispatcher(); $dispatcher->setNamespacePrefix('App\\HttpController'); $http = new Server('127.0.0.1', 9501); $http->set(['worker_num' => 1]); $http->on('request', function ($request, $response) use ($dispatcher) { $requestPsr = new Request($request); $responsePsr = new Response($response); $dispatcher->dispatch($requestPsr, $responsePsr); $responsePsr->__response(); }); $http->start();
控制器路径、命名空间及文档 host 需要与业务项目保持一致。本仓库的可运行文档生成示例见 test.php,控制器示例见 tests/ControllerExample。
验证
开发测试使用 PHPUnit 13.4,需要 PHP 8.4 或以上;库本身的最低 PHP 版本仍为 8.1。安装开发依赖后,默认读取 phpunit.xml.dist,可运行完整测试或指定目录:
php vendor/bin/phpunit php vendor/bin/phpunit tests/Attributes php vendor/bin/phpunit tests/Document node tests/Document/try-runner.test.cjs node tests/Document/document-ui.test.cjs
Node 测试使用内置的 Fetch、File、FormData 等 API,需使用提供这些全局对象的现代 Node.js(建议 20+)。前端交互测试采用 DOM 替身;浏览器焦点、布局和跨域行为仍需在实际浏览器与服务环境中验证。
验证器测试通过 Param(type: null, ...) 保留原始输入类型,专门验证规则行为;默认 ParamType::STRING 会先将输入转换为字符串。PHPUnit 数据提供器使用 #[DataProvider(...)],提供器方法必须为 public static。
参数来源包含 ParamFrom::FILE 时,只允许单一来源 from: [ParamFrom::FILE](兼容 from: ParamFrom::FILE);与其他来源混用或重复定义 FILE 会抛出异常。