自动注入注解
自动注入注解标注在控制器方法参数上,框架在容器解析参数时自动从 HTTP 请求的指定数据源取值,并配合类型声明与验证规则完成校验。它们是编写控制器时最常用的注解族。
注解总览
四个注解均位于 Viswoole\HttpServer\AutoInject 命名空间,标注目标为方法参数(TARGET_PARAMETER)与类属性(TARGET_PROPERTY)。注解自身没有构造参数,取值字段名取自方法参数名:
| 注解 | 数据源 | 底层调用 | 缺失时的错误消息 |
|---|---|---|---|
#[InjectGet] | URL 查询参数(?key=value) | Request::get() | (GET)请求参数{name}不能为空 |
#[InjectPost] | POST 请求体(JSON/表单) | Request::post() | 请求参数{name}不能为空 |
#[InjectHeader] | 请求标头 | Request::getHeader() | 请求头{name}不能为空 |
#[InjectFile] | 上传文件 | Request::files() | 必须上传{name}文件 |
错误消息中的 {name} 会替换为实际的参数名。
自动映射为 API 文档参数来源
开启 API 文档后,注解来源自动映射为参数位置(InjectGet → query、InjectPost → body、InjectHeader → header、InjectFile → file),参数描述取自方法 PHPDoc 的 @param 注释。详见 API 文档生成。
工作机制
注入发生在容器 injectParams() 的「前置注入」阶段,四个注解实现统一的注入签名(以 InjectGet 为例):
// 源码:Viswoole\HttpServer\AutoInject\InjectGet
public function inject(string $name, mixed $value, bool $allowNull): mixed
{
$value = Request::get($name, $value); // 以参数当前值为兜底
return $this->validateEmpty($value, $allowNull, "(GET)请求参数{$name}不能为空");
}$name:方法参数名,即请求数据的字段名;$value:注入前的当前值(动态路由变量或方法默认值),作为数据源缺失时的兜底;$allowNull:来自参数类型声明的可空性(allowsNull()),决定缺失时是否抛异常;- 取值仍为
null且不允许为空时,抛出Viswoole\Core\Exception\ValidateException(ValidateNulltrait 统一处理)。
因此实际取值优先级为:注解数据源 > 动态路由变量 > 方法默认值。
参数缺失行为
| 参数声明 | 数据源缺失时 | 示例 |
|---|---|---|
| 非空类型且无默认值 | 抛出 ValidateException | #[InjectGet] string $keyword |
| 声明了默认值 | 使用默认值 | #[InjectGet] int $page = 1 |
可空类型(?type) | 值为 null | #[InjectGet] ?string $keyword |
默认值为 null | 值为 null | #[InjectGet] ?string $keyword = null |
ValidateException 由 HTTP 异常处理器渲染为 400 状态码的 JSON 响应:
{
"code": 0,
"message": "(GET)请求参数keyword不能为空"
}InjectGet:查询参数注入
从 URL 查询字符串取值,值会按参数声明的内置类型自动转换(如 "123" → 123):
use Viswoole\HttpServer\AutoInject\InjectGet;
use Viswoole\Router\Annotation\AutoController;
#[AutoController(prefix: 'search')]
class SearchController
{
/**
* 搜索
*
* GET /search/query?keyword=viswoole&page=2
*
* @param string $keyword 搜索关键词
* @param int $page 页码
* @param string|null $category 分类,可选
*/
public function query(
#[InjectGet] string $keyword,
#[InjectGet] int $page = 1,
#[InjectGet] ?string $category = null,
): array {
return compact('keyword', 'page', 'category');
}
}InjectPost:请求体注入
从 POST 请求体取值。请求的 Content-Type 以 application/json 开头时,框架在构造 Request 对象阶段就会把 JSON 包体解析并填充到 POST 数据中(源码 Request::__construct()),因此 JSON 与传统表单(multipart/form-data、application/x-www-form-urlencoded)的取值方式完全一致:
use Viswoole\HttpServer\AutoInject\InjectPost;
use Viswoole\Router\Annotation\RouteMapping;
// POST /user/create,Body: {"name": "张三", "tags": ["vip", "svip"]}
#[RouteMapping(method: 'POST')]
public function create(
#[InjectPost] string $name,
#[InjectPost] array $tags,
): array {
return ['name' => $name, 'tags' => $tags];
}复杂请求体建议映射为 DTO 对象(在构造参数上标注验证注解),配合 #[InjectPost] 一次性完成验证与装配,详见验证器。
InjectHeader:请求标头注入
从请求标头取值,返回 string|null。标头键名统一以小写存储与匹配,参数名不区分大小写:
use Viswoole\HttpServer\AutoInject\InjectHeader;
#[RouteMapping(method: 'GET')]
public function profile(#[InjectHeader] string $authorization): array
{
$token = str_replace('Bearer ', '', $authorization);
return ['token_prefix' => substr($token, 0, 8)];
}带连字符的请求头无法按参数名注入
PHP 变量名不允许出现连字符,X-Request-Id 这类标头无法声明为参数名,请改用 Request::getHeader('X-Request-Id') 手动获取。详见 Request 请求对象。
InjectFile:上传文件注入
从上传文件中取值:字段对应单个文件时返回 UploadedFile,多个文件时返回 UploadedFile[]。配合 #[FileRule] 可校验 MIME、大小与数量:
use Viswoole\HttpServer\AutoInject\InjectFile;
use Viswoole\HttpServer\Message\UploadedFile;
use Viswoole\HttpServer\Validate\FileRule;
#[RouteMapping(method: 'POST')]
public function upload(
#[FileRule(fileMime: 'image/png|image/jpeg', maxSize: 5242880), InjectFile]
UploadedFile $avatar,
): array {
return ['filename' => $avatar->getClientFilename()];
}UploadedFile 的完整用法见文件上传。
与验证规则注解组合
注入注解只负责「取值」,格式校验交给 BaseValidateRule 验证注解,二者可以叠加在同一个参数上,注入完成后依次执行:
use Viswoole\Core\Validate\Rules\{Length, Max, Min};
public function search(
#[InjectGet, Length(1, 50)] string $keyword, // 长度 1-50
#[InjectGet, Min(1), Max(100)] int $page = 1, // 页码范围
): array {}验证失败抛出 ValidateException(400 响应)。内置规则与自定义规则见验证器。
注意事项
- 不做 XSS 过滤:注入值直接取自 GET/POST 数据源,不经过任何过滤器;需要过滤后的值请使用
Request::param(); - 注入不等于验证:注入仅保证「有值且类型匹配」,格式约束必须显式添加验证注解;
- 可空语义:想允许字段缺失,就声明可空类型或给出默认值;非空且无默认值的参数缺失会直接返回 400。
