自动注入注解

自动注入注解标注在控制器方法参数上,框架在容器解析参数时自动从 HTTP 请求的指定数据源取值,并配合类型声明与验证规则完成校验。它们是编写控制器时最常用的注解族。

注解总览

四个注解均位于 Viswoole\HttpServer\AutoInject 命名空间,标注目标为方法参数(TARGET_PARAMETER)与类属性(TARGET_PROPERTY)。注解自身没有构造参数,取值字段名取自方法参数名:

注解数据源底层调用缺失时的错误消息
#[InjectGet]URL 查询参数(?key=valueRequest::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 为例):

php
// 源码: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\ValidateExceptionValidateNull trait 统一处理)。

因此实际取值优先级为:注解数据源 > 动态路由变量 > 方法默认值

参数缺失行为

参数声明数据源缺失时示例
非空类型且无默认值抛出 ValidateException#[InjectGet] string $keyword
声明了默认值使用默认值#[InjectGet] int $page = 1
可空类型(?type值为 null#[InjectGet] ?string $keyword
默认值为 null值为 null#[InjectGet] ?string $keyword = null

ValidateException 由 HTTP 异常处理器渲染为 400 状态码的 JSON 响应:

json
{
  "code": 0,
  "message": "(GET)请求参数keyword不能为空"
}

InjectGet:查询参数注入

从 URL 查询字符串取值,值会按参数声明的内置类型自动转换(如 "123"123):

php
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-Typeapplication/json 开头时,框架在构造 Request 对象阶段就会把 JSON 包体解析并填充到 POST 数据中(源码 Request::__construct()),因此 JSON 与传统表单(multipart/form-dataapplication/x-www-form-urlencoded)的取值方式完全一致:

php
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。标头键名统一以小写存储与匹配,参数名不区分大小写:

php
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、大小与数量:

php
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 验证注解,二者可以叠加在同一个参数上,注入完成后依次执行:

php
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。