自动参数注入
Viswoole 框架提供了强大的自动参数注入功能,通过注解声明式地从 HTTP 请求的不同数据源获取参数,并自动进行类型转换和验证。这大大简化了控制器代码,让开发者专注于业务逻辑。
注入注解概览
| 注解 | 数据源 | 接口 | 典型用途 |
|---|---|---|---|
#[InjectGet] | URL 查询参数(?key=value) | QueryParamInterface | 获取 GET 请求参数 |
#[InjectPost] | POST 请求体(JSON/表单) | BodyParamInterface | 获取 POST 请求数据 |
#[InjectHeader] | HTTP 请求头 | HeaderParamInterface | 获取自定义请求头 |
#[InjectFile] | 上传文件 | FileParamInterface | 获取上传的文件对象 |
基础用法
InjectGet - 查询参数注入
从 URL 查询字符串中获取参数:
php
<?php
declare(strict_types=1);
namespace App\Controller;
use Viswoole\HttpServer\AutoInject\InjectGet;
use Viswoole\Router\Annotation\AutoController;
#[AutoController]
class SearchController
{
/**
* 搜索接口
*
* GET /search/query?keyword=php&page=1&size=10
*
* @param string $keyword 搜索关键词
* @param int $page 页码
* @param int $size 每页数量
* @return array 搜索结果
*/
public static function query(
#[InjectGet] string $keyword,
#[InjectGet] int $page = 1, // 支持默认值
#[InjectGet] int $size = 10 // 支持默认值
): array {
return [
'keyword' => $keyword,
'page' => $page,
'size' => $size,
'results' => []
];
}
}请求示例:
text
GET /search/query?keyword=viswoole&page=2&size=20InjectPost - 请求体注入
从 POST 请求体中获取参数(支持 JSON 和表单):
php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Interface\UserInfo;
use Viswoole\HttpServer\AutoInject\InjectPost;
use Viswoole\Router\Annotation\AutoController;
#[AutoController]
class UserController
{
/**
* 创建用户
*
* POST /user/create
* Content-Type: application/json
* Body: { "name": "张三", "email": "test@example.com" }
*
* @param UserInfo $userInfo 用户信息 DTO
* @return array 创建结果
*/
#[RouteMapping(method: 'POST')]
public static function create(#[InjectPost] UserInfo $userInfo): array
{
// userInfo 已经过验证和类型转换
return [
'name' => $userInfo->name,
'email' => $userInfo->email ?? '',
'message' => '创建成功'
];
}
/**
* 简单参数注入
*
* POST /user/update
* Body: { "id": 100, "name": "新名称" }
*/
#[RouteMapping(method: 'POST')]
public static function update(
#[InjectPost] int $id,
#[InjectPost] string $name
): array {
return ['id' => $id, 'name' => $name];
}
}InjectHeader - 请求头注入
从 HTTP 请求头中获取参数:
php
<?php
declare(strict_types=1);
namespace App\Controller;
use Viswoole\HttpServer\AutoInject\InjectHeader;
use Viswoole\Router\Annotation\AutoController;
#[AutoController]
class ApiController
{
/**
* API 接口(需要认证)
*
* Headers:
* Authorization: Bearer xxxxx
* X-Request-Id: uuid-string
* Accept-Language: zh-CN
*/
public static function data(
#[InjectHeader] string $authorization,
#[InjectHeader] string $xRequestId,
#[InjectHeader] ?string $acceptLanguage = 'zh-CN'
): array {
// 解析 Bearer Token
$token = str_replace('Bearer ', '', $authorization);
return [
'token' => substr($token, 0, 10) . '...',
'request_id' => $xRequestId,
'language' => $acceptLanguage
];
}
}注意:InjectHeader 无构造函数参数,参数名必须与 header 名一致(不区分大小写)。例如参数名 $xRequestId 会匹配请求头 X-Request-Id。
支持的数据类型
基本类型
框架自动将字符串参数转换为声明的类型:
php
public static function test(
#[InjectGet] string $name, // 字符串
#[InjectGet] int $age, // 整数
#[InjectGet] float $price, // 浮点数
#[InjectGet] bool $isActive, // 布尔值("1"/"true" → true)
#[InjectGet] ?int $optionalId // 可空类型
): array { ... }类型转换规则:
int: 字符串数字 → 整数(如"123"→123)float: 字符串数字 → 浮点数(如"19.99"→19.99)bool:"1","true","on"→true; 其他 →falsestring: 保持原值(经过 XSS 过滤)
数组类型
对于数组参数,使用数组语法传递:
php
/**
* 批量操作
*
* POST /batch/delete
* Body: { "ids": [1, 2, 3, 4, 5] }
*/
#[RouteMapping(method: 'POST')]
public static function batchDelete(#[InjectPost] array $ids): array
{
return ['deleted' => count($ids), 'ids' => $ids];
}对象类型(DTO)
将请求数据自动映射到 DTO 对象,这是最强大的特性:
定义 DTO 类
php
<?php
declare(strict_types=1);
namespace App\Interface;
use Viswoole\Core\Validate\Rules\Chinese;
/**
* 创建用户 DTO
*
* 属性会根据验证注解自动校验
*/
class CreateUserDto
{
/**
* @param Chinese $name 姓名(必须是中文)
* @param string $email 邮箱地址(必须符合邮箱格式)
* @param string $gender 性别
* @param int|null $age 年龄(可选)
*/
public function __construct(
#[Chinese(message: '姓名必须是中文')] public readonly string $name,
#[Regex(pattern: "^\w+([-+.]\w+)*@\w+[.]{1,3}\w+$", message: "邮箱格式不正确")] public readonly string $email,
#[Alpha] public readonly string $gender,
public readonly ?int $age = null
) {}
}在控制器中使用
php
/**
* 创建用户
*
* POST /user/create
* Content-Type: application/json
* Body: {
* "name": "张三",
* "email": "zhangsan@example.com",
* "gender": "male",
* "age": 25
* }
*/
#[RouteMapping(method: 'POST')]
public static function create(#[InjectPost] CreateUserDto $dto): array
{
// 所有属性已经过验证,可直接安全使用
return [
'name' => $dto->name, // "张三"
'email' => $dto->email, // "zhangsan@example.com"
'gender' => $dto->gender, // "male"
'age' => $dto->age // 25
];
}验证失败时: 如果请求数据不符合验证规则,框架会抛出 ValidateException,返回 400 错误和详细的错误信息。
空值校验
必填参数 vs 可选参数
当参数不允许为空但值为 null 时,框架会抛出 ValidateException:
php
// ✅ 必填:参数缺失或为空时报错
public static function required(#[InjectGet] string $name): string
{
return $name;
}
// ✅ 可选:使用默认值
public static function optional(#[InjectGet] string $name = 'default'): string
{
return $name;
}
// ✅ 可空:允许 null 值
public static function nullable(#[InjectGet] ?string $name = null): ?string
{
return $name;
}错误响应示例
当必填参数缺失时:
json
{
"code": 400,
"message": "(GET)请求参数name不能为空",
"error": "ValidateException"
}默认值
所有注入类型都支持默认值:
php
public static function list(
#[InjectGet] int $page = 1, // 默认第 1 页
#[InjectGet] int $size = 10, // 默认每页 10 条
#[InjectGet] string $sort = 'created_at', // 默认排序字段
#[InjectGet] string $order = 'desc', // 默认降序
#[InjectGet] ?string $keyword = null // 可选搜索关键词
): array {
return compact('page', 'size', 'sort', 'order', 'keyword');
}完整示例:综合使用所有注入类型
php
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Interface\OrderDto;
use Viswoole\HttpServer\AutoInject\InjectGet;
use Viswoole\HttpServer\AutoInject\InjectPost;
use Viswoole\HttpServer\AutoInject\InjectHeader;
use Viswoole\HttpServer\AutoInject\InjectFile;
use Viswoole\HttpServer\Message\UploadedFile;
use Viswoole\HttpServer\Contract\ResponseInterface;
use Viswoole\Router\Annotation\AutoController;
use Viswoole\Router\Annotation\RouteMapping;
use App\Response;
/**
* 订单控制器 - 演示所有注入类型
*/
#[AutoController(prefix: 'api/v1')]
class OrderController
{
/**
* 创建订单(综合示例)
*
* POST /api/v1/order/create
*
* Query: ?couponCode=SAVE20
* Headers:
* X-Request-Id: req-123456
* Authorization: Bearer token-xxx
* Body (JSON): {
* "productId": 1001,
* "quantity": 2,
* "address": "北京市朝阳区..."
* }
* Files: receipt (发票文件)
*/
#[RouteMapping(method: 'POST')]
public static function create(
// GET 参数:优惠码
#[InjectGet] ?string $couponCode = null,
// Header 参数
#[InjectHeader] string $xRequestId,
#[InjectHeader] string $authorization,
// POST 参数(DTO 对象)
#[InjectPost] OrderDto $order,
// 文件上传
#[InjectFile] UploadedFile $receipt,
// 依赖注入:Response 对象
Response $response
): ResponseInterface {
// 处理业务逻辑...
$token = str_replace('Bearer ', '', $authorization);
$orderId = uniqid('order_');
// 移动上传文件
$uploadPath = app()->getRootPath() . "/runtime/uploads/receipts/{$orderId}_receipt.pdf";
$receipt->moveTo($uploadPath);
return $response->json([
'order_id' => $orderId,
'request_id' => $xRequestId,
'product' => $order->productId,
'quantity' => $order->quantity,
'coupon' => $couponCode ?? '未使用',
'receipt_file' => $receipt->getClientFilename(),
'token_prefix' => substr($token, 0, 8) . '...'
]);
}
}最佳实践
1. 使用 DTO 对象管理复杂请求体
对于包含多个字段的请求,始终定义 DTO 类:
php
// ✅ 推荐:DTO 对象
public static function create(#[InjectPost] CreateUserDto $dto): array { ... }
// ❌ 避免:过多独立参数
public static function create(
#[InjectPost] string $name,
#[InjectPost] string $email,
#[InjectPost] string $phone,
#[InjectPost] int $age,
#[InjectPost] string $address,
#[InjectPost] string $city,
// ... 更多参数
): array { ... }2. 合理设置默认值
php
// ✅ 推荐:合理的默认值
public static function list(
#[InjectGet] int $page = 1, // 从第一页开始
#[InjectGet] int $size = 10, // 合理的分页大小
#[InjectGet] string $sort = 'id', // 默认按 ID 排序
#[InjectGet] string $order = 'DESC' // 默认降序
): array { ... }3. 使用可空类型处理可选字段
php
// ✅ 推荐:可选字段使用 ?type
public static function search(
#[InjectGet] string $keyword,
#[InjectGet] ?string $category = null, // 可选分类
#[InjectGet] ?int $minPrice = null, // 可选最低价
#[InjectGet] ?int $maxPrice = null // 可选最高价
): array { ... }4. 验证注解与 DTO 结合使用
在 DTO 构造函数参数上添加验证注解:
php
class ProductDto
{
public function __construct(
#[Min(1)] public readonly int $id,
#[Length(2, 100)] public readonly string $name,
#[Min(0.01)] public readonly float $price,
#[InArray(['active', 'inactive'])] public readonly string $status
) {}
}常见问题
Q: 可以同时使用多个相同类型的注入注解吗?
A: 可以,每个参数使用独立的注解:
php
public static function test(
#[InjectGet] string $param1,
#[InjectGet] int $param2,
#[InjectGet] bool $param3
): array { ... }Q: 如何获取原始未过滤的值?
A: 直接使用 Request 对象的方法,绕过注入系统:
php
use Viswoole\HttpServer\Facade\Request;
public static function raw(): mixed
{
// 获取未经 htmlspecialchars 过滤的原始值
return Request::post('content', null, []); // 空数组表示不使用过滤器
}Q: 注入的参数会被 XSS 过滤吗?
A: 是的,默认会对字符串类型应用 htmlspecialchars 过滤。如果需要原始值(如富文本内容),使用 Request 对象并传入空的过滤器数组。
Q: DTO 对象支持嵌套吗?
A: 支持,只要嵌套对象的构造函数也符合注入规范即可。详见框架的验证规则文档。
