tangwei/dto

php hyperf dto

Maintainers

Details

github.com/tw2066/dto

Source

Issues

Installs: 123 536

Dependents: 2

Suggesters: 0

Security: 0

Stars: 14

Watchers: 2

Forks: 6

Open Issues: 0

pkg:composer/tangwei/dto


README

Latest Stable Version Total Downloads License PHP Version

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

安装后,组件会自动注册,无需额外配置。

📖 快速开始

基本使用

1. 创建 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;
}

2. 在控制器中使用

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

RequestBody

获取 POST/PUT/PATCH 请求的 Body 参数

use Hyperf\DTO\Annotation\Contracts\RequestBody;

#[PostMapping(path: 'create')]
public function create(#[RequestBody] CreateUserRequest $request)
{
    // $request 会自动填充 Body 中的数据
}

RequestQuery

获取 URL 查询参数(GET 参数)

use Hyperf\DTO\Annotation\Contracts\RequestQuery;

#[GetMapping(path: 'list')]
public function list(#[RequestQuery] QueryRequest $request)
{
    // $request 会自动填充 Query 参数
}

RequestFormData

获取表单请求数据(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') 获取
}

RequestHeader

获取请求头信息

use Hyperf\DTO\Annotation\Contracts\RequestHeader;

#[GetMapping(path: 'info')]
public function info(#[RequestHeader] HeaderRequest $headers)
{
    // $headers 会自动填充请求头数据
}

Valid

启用验证,必须与其他参数来源注解一起使用

#[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 参数
}

⚠️ 注意:同一个方法不能同时使用 RequestBodyRequestFormData 注解

📝 完整示例

控制器示例

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'];
    }
}

DTO 类示例

简单 DTO

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;
}

嵌套对象 DTO

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;
}

数组类型 DTO

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 注解

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;
}

🔧 高级功能

RPC 支持

在 JSON-RPC 服务中返回 PHP 对象,需要配置序列化支持。

1. 安装依赖

composer require symfony/serializer ^5.0|^6.0
composer require symfony/property-access ^5.0|^6.0

2. 配置 Aspect

config/autoload/aspects.php 中添加:

return [
    \Hyperf\DTO\Aspect\ObjectNormalizerAspect::class,
];

3. 配置依赖

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;
}

💡 最佳实践

1. DTO 类结构设计

  • 为不同的请求类型创建独立的 DTO 类
  • 使用有意义的类名,如 CreateUserRequestUpdateUserRequest
  • 将 Request DTO 和 Response DTO 分开存放

2. 验证规则

  • 优先使用内置验证注解,保持代码可读性
  • 复杂验证使用 Validation 注解
  • 通用验证规则封装为自定义注解

3. 错误处理

验证失败会抛出 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;
    }
}

📚 常见问题

Q: 为什么验证没有生效?

A: 请确保:

  1. 已安装 hyperf/validation 组件
  2. 在控制器方法参数上添加了 #[Valid] 注解
  3. DTO 类中的属性添加了验证注解

Q: 如何处理嵌套数组?

A: 使用 PHPDoc 或 ArrayType 注解:

/**
 * @var User[]
 */
public array $users;

// 或者
#[ArrayType(User::class)]
public array $users;

Q: 可以同时使用 RequestBody 和 RequestFormData 吗?

A: 不可以。这两个注解是互斥的,因为它们处理不同的请求类型。

Q: 如何处理文件上传?

A: 使用 RequestFormData 注解,然后通过 $this->request->file() 获取文件。

🔗 相关链接