English | 中文
基于 Hyperf 框架的 DTO (数据传输对象) 映射和验证库,使用 PHP 8.1+ 的属性(Attributes)特性,提供优雅的请求参数绑定和验证方案。
- 🚀 自动映射 - 请求参数自动映射到 PHP DTO 类
- 🎯 类型安全 - 利用 PHP 8.1+ 的类型系统,提供完整的类型提示
- 🔄 递归支持 - 支持数组、嵌套对象、递归结构
- ✅ 数据验证 - 集成 Hyperf 验证器,提供丰富的验证注解
- 📝 多种参数源 - 支持 Body、Query、FormData、Header 等多种参数来源
- 🎨 代码优雅 - 基于 PHP 8 Attributes,代码简洁易读
- 🔧 易于扩展 - 支持自定义验证规则和类型转换
- PHP >= 8.1
- Hyperf
composer require tangwei/dto安装后,组件会自动注册,无需额外配置。
namespace App\Request;
use Hyperf\DTO\Annotation\Validation\Required;
use Hyperf\DTO\Annotation\Validation\Integer;
use Hyperf\DTO\Annotation\Validation\Between;
class DemoQuery
{
public string $name;
#[Required]
#[Integer]
#[Between(1, 100)]
public int $age;
}namespace App\Controller;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Hyperf\DTO\Annotation\Contracts\RequestQuery;
use Hyperf\DTO\Annotation\Contracts\Valid;
use App\Request\DemoQuery;
#[Controller(prefix: '/user')]
class UserController
{
#[GetMapping(path: 'info')]
public function info(#[RequestQuery] #[Valid] DemoQuery $request): array
{
return [
'name' => $request->name,
'age' => $request->age,
];
}
}命名空间:
Hyperf\DTO\Annotation\Contracts
获取 POST/PUT/PATCH 请求的 Body 参数
use Hyperf\DTO\Annotation\Contracts\RequestBody;
#[PostMapping(path: 'create')]
public function create(#[RequestBody] CreateUserRequest $request)
{
// $request 会自动填充 Body 中的数据
}获取 URL 查询参数(GET 参数)
use Hyperf\DTO\Annotation\Contracts\RequestQuery;
#[GetMapping(path: 'list')]
public function list(#[RequestQuery] QueryRequest $request)
{
// $request 会自动填充 Query 参数
}获取表单请求数据(Content-Type: multipart/form-data)
use Hyperf\DTO\Annotation\Contracts\RequestFormData;
#[PostMapping(path: 'upload')]
public function upload(#[RequestFormData] UploadRequest $formData)
{
// $formData 会自动填充表单数据
// 文件上传需要通过 $this->request->file('field_name') 获取
}获取请求头信息
use Hyperf\DTO\Annotation\Contracts\RequestHeader;
#[GetMapping(path: 'info')]
public function info(#[RequestHeader] HeaderRequest $headers)
{
// $headers 会自动填充请求头数据
}启用验证,必须与其他参数来源注解一起使用
#[PostMapping(path: 'create')]
public function create(#[RequestBody] #[Valid] CreateUserRequest $request)
{
// 请求参数会先验证,验证失败会自动抛出异常
}可以在同一方法中组合使用多种参数来源:
#[PutMapping(path: 'update/{id}')]
public function update(
int $id,
#[RequestBody] #[Valid] UpdateRequest $body,
#[RequestQuery] QueryRequest $query,
#[RequestHeader] HeaderRequest $headers
) {
// 同时获取 Body、Query 和 Header 参数
}
⚠️ 注意:同一个方法不能同时使用RequestBody和RequestFormData注解
namespace App\Controller;
use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use Hyperf\HttpServer\Annotation\PostMapping;
use Hyperf\HttpServer\Annotation\PutMapping;
use Hyperf\DTO\Annotation\Contracts\RequestBody;
use Hyperf\DTO\Annotation\Contracts\RequestQuery;
use Hyperf\DTO\Annotation\Contracts\RequestFormData;
use Hyperf\DTO\Annotation\Contracts\Valid;
#[Controller(prefix: '/demo')]
class DemoController
{
#[GetMapping(path: 'query')]
public function query(#[RequestQuery] #[Valid] DemoQuery $request): array
{
return [
'name' => $request->name,
'age' => $request->age,
];
}
#[PostMapping(path: 'create')]
public function create(#[RequestBody] #[Valid] CreateRequest $request): array
{
// 处理创建逻辑
return ['id' => 1, 'message' => 'Created successfully'];
}
#[PutMapping(path: 'update')]
public function update(
#[RequestBody] #[Valid] UpdateRequest $body,
#[RequestQuery] QueryParams $query
): array {
// 同时使用 Body 和 Query 参数
return ['message' => 'Updated successfully'];
}
#[PostMapping(path: 'upload')]
public function upload(#[RequestFormData] UploadRequest $formData): array
{
$file = $this->request->file('photo');
// 处理文件上传
return ['message' => 'Uploaded successfully'];
}
}namespace App\Request;
use Hyperf\DTO\Annotation\Validation\Required;
use Hyperf\DTO\Annotation\Validation\Integer;
use Hyperf\DTO\Annotation\Validation\Between;
use Hyperf\DTO\Annotation\Validation\Email;
class CreateRequest
{
#[Required]
public string $name;
#[Required]
#[Email]
public string $email;
#[Required]
#[Integer]
#[Between(18, 100)]
public int $age;
}namespace App\Request;
class UserRequest
{
public string $name;
public int $age;
// 嵌套对象
public Address $address;
}
class Address
{
public string $province;
public string $city;
public string $street;
}namespace App\Request;
use Hyperf\DTO\Annotation\ArrayType;
class BatchRequest
{
/**
* @var int[]
*/
public array $ids;
/**
* @var User[]
*/
public array $users;
// 使用 ArrayType 注解显式指定类型
#[ArrayType(User::class)]
public array $members;
}namespace App\Request;
use Hyperf\DTO\Annotation\JSONField;
class ApiRequest
{
// 将请求中的 user_name 映射到 userName
#[JSONField('user_name')]
public string $userName;
#[JSONField('user_age')]
public int $userAge;
}需要先安装 Hyperf 验证器:
composer require hyperf/validation
本库提供了丰富的验证注解,包括:
Required- 必填项Integer- 整数Numeric- 数字Between- 范围验证Min/Max- 最小/最大值Email- 邮箱格式Url- URL 格式Date- 日期格式DateFormat- 指定日期格式Boolean- 布尔值Alpha- 字母AlphaNum- 字母和数字AlphaDash- 字母、数字、破折号、下划线Image- 图片文件Json- JSON 格式Nullable- 可为空In- 在指定值中NotIn- 不在指定值中Regex- 正则表达式Unique- 数据库唯一Exists- 数据库存在
use Hyperf\DTO\Annotation\Validation\Required;
use Hyperf\DTO\Annotation\Validation\Integer;
use Hyperf\DTO\Annotation\Validation\Between;
class DemoQuery
{
#[Required]
public string $name;
#[Required]
#[Integer]
#[Between(1, 100)]
public int $age;
}在控制器中使用 #[Valid] 注解启用验证:
#[GetMapping(path: 'query')]
public function query(#[RequestQuery] #[Valid] DemoQuery $request)
{
// 参数已经验证通过
}class UserRequest
{
#[Required("用户名不能为空”)]
public string $name;
#[Between(18, 100, "年龄必须在 18-100 之间")]
public int $age;
}Validation 注解支持 Laravel 风格的验证规则:
use Hyperf\DTO\Annotation\Validation\Validation;
class ComplexRequest
{
// 使用管道符分隔多个规则
#[Validation("required|string|min:3|max:50”)]
public string $username;
// 数组元素验证
#[Validation("integer”, customKey: 'ids.*')]
public array $ids;
}继承 BaseValidation 类即可创建自定义验证规则:
namespace App\Validation;
use Attribute;
use Hyperf\DTO\Annotation\Validation\BaseValidation;
#[Attribute(Attribute::TARGET_PROPERTY | Attribute::IS_REPEATABLE)]
class Phone extends BaseValidation
{
protected $rule = 'regex:/^1[3-9]\\d{9}$/';
public function __construct(string $messages = '手机号格式不正确')
{
parent::__construct($messages);
}
}使用自定义验证:
use App\Validation\Phone;
class RegisterRequest
{
#[Required]
#[Phone]
public string $mobile;
}在 JSON-RPC 服务中返回 PHP 对象,需要配置序列化支持。
composer require symfony/serializer ^5.0|^6.0
composer require symfony/property-access ^5.0|^6.0在 config/autoload/aspects.php 中添加:
return [
\Hyperf\DTO\Aspect\ObjectNormalizerAspect::class,
];在 config/autoload/dependencies.php 中添加:
use Hyperf\Serializer\SerializerFactory;
use Hyperf\Serializer\Serializer;
return [
Hyperf\Contract\NormalizerInterface::class => new SerializerFactory(Serializer::class),
];如果需要自定义类型转换逻辑,可以实现自己的转换器:
namespace App\Convert;
use Hyperf\DTO\Type\ConvertCustom;
class CustomConvert implements ConvertCustom
{
public function convert(mixed $value): mixed
{
// 自定义转换逻辑
return $value;
}
}在 DTO 类中使用:
use Hyperf\DTO\Annotation\Dto;
use Hyperf\DTO\Type\Convert;
#[Dto(Convert::SNAKE)]
class UserResponse
{
public string $name;
public int $age;
}- 为不同的请求类型创建独立的 DTO 类
- 使用有意义的类名,如
CreateUserRequest、UpdateUserRequest - 将 Request DTO 和 Response DTO 分开存放
- 优先使用内置验证注解,保持代码可读性
- 复杂验证使用
Validation注解 - 通用验证规则封装为自定义注解
验证失败会抛出 Hyperf\Validation\ValidationException 异常,可以通过异常处理器统一处理:
namespace App\Exception\Handler;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\Validation\ValidationException;
use Psr\Http\Message\ResponseInterface;
use Hyperf\HttpMessage\Stream\SwooleStream;
class ValidationExceptionHandler extends ExceptionHandler
{
public function handle(\Throwable $throwable, ResponseInterface $response)
{
if ($throwable instanceof ValidationException) {
$this->stopPropagation();
return $response->withStatus(422)->withBody(
new SwooleStream(json_encode([
'code' => 422,
'message' => 'Validation failed',
'errors' => $throwable->validator->errors()->toArray(),
]))
);
}
return $response;
}
public function isValid(\Throwable $throwable): bool
{
return $throwable instanceof ValidationException;
}
}A: 请确保:
- 已安装
hyperf/validation组件 - 在控制器方法参数上添加了
#[Valid]注解 - DTO 类中的属性添加了验证注解
A: 使用 PHPDoc 或 ArrayType 注解:
/**
* @var User[]
*/
public array $users;
// 或者
#[ArrayType(User::class)]
public array $users;A: 不可以。这两个注解是互斥的,因为它们处理不同的请求类型。
A: 使用 RequestFormData 注解,然后通过 $this->request->file() 获取文件。