English | 中文
A Data Transfer Object (DTO) mapping and validation library for the Hyperf framework, leveraging PHP 8.1+ Attributes to provide an elegant solution for request parameter binding and validation.
- 🚀 Auto Mapping - Automatically map request parameters to PHP DTO classes
- 🎯 Type Safety - Leverage PHP 8.1+ type system for complete type hints
- 🔄 Recursive Support - Support for arrays, nested objects, and recursive structures
- ✅ Data Validation - Integrate with Hyperf validator, providing rich validation annotations
- 📝 Multiple Parameter Sources - Support Body, Query, FormData, Header, and more
- 🎨 Elegant Code - Based on PHP 8 Attributes for clean and readable code
- 🔧 Easy to Extend - Support custom validation rules and type conversion
- PHP >= 8.1
- Hyperf >= 3.0
- Swoole >= 5.0
composer require tangwei/dtoAfter installation, the component will be automatically registered without any additional configuration.
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,
];
}
}Namespace:
Hyperf\DTO\Annotation\Contracts
Retrieve parameters from POST/PUT/PATCH request body
use Hyperf\DTO\Annotation\Contracts\RequestBody;
#[PostMapping(path: 'create')]
public function create(#[RequestBody] CreateUserRequest $request)
{
// $request will be automatically populated with data from the body
}Retrieve URL query parameters (GET parameters)
use Hyperf\DTO\Annotation\Contracts\RequestQuery;
#[GetMapping(path: 'list')]
public function list(#[RequestQuery] QueryRequest $request)
{
// $request will be automatically populated with query parameters
}Retrieve form request data (Content-Type: multipart/form-data)
use Hyperf\DTO\Annotation\Contracts\RequestFormData;
#[PostMapping(path: 'upload')]
public function upload(#[RequestFormData] UploadRequest $formData)
{
// $formData will be automatically populated with form data
// File uploads need to be retrieved via $this->request->file('field_name')
}Retrieve request header information
use Hyperf\DTO\Annotation\Contracts\RequestHeader;
#[GetMapping(path: 'info')]
public function info(#[RequestHeader] HeaderRequest $headers)
{
// $headers will be automatically populated with request header data
}Enable validation, must be used together with other parameter source annotations
#[PostMapping(path: 'create')]
public function create(#[RequestBody] #[Valid] CreateUserRequest $request)
{
// Request parameters will be validated first; validation failure will throw an exception
}You can combine multiple parameter sources in the same method:
#[PutMapping(path: 'update/{id}')]
public function update(
int $id,
#[RequestBody] #[Valid] UpdateRequest $body,
#[RequestQuery] QueryRequest $query,
#[RequestHeader] HeaderRequest $headers
) {
// Retrieve Body, Query, and Header parameters simultaneously
}
⚠️ Note: The same method cannot use bothRequestBodyandRequestFormDataannotations
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
{
// Handle creation logic
return ['id' => 1, 'message' => 'Created successfully'];
}
#[PutMapping(path: 'update')]
public function update(
#[RequestBody] #[Valid] UpdateRequest $body,
#[RequestQuery] QueryParams $query
): array {
// Use both Body and Query parameters
return ['message' => 'Updated successfully'];
}
#[PostMapping(path: 'upload')]
public function upload(#[RequestFormData] UploadRequest $formData): array
{
$file = $this->request->file('photo');
// Handle file upload
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;
// Nested object
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;
// Use ArrayType annotation to explicitly specify type
#[ArrayType(User::class)]
public array $members;
}namespace App\Request;
use Hyperf\DTO\Annotation\JSONField;
class ApiRequest
{
// Map user_name from the request to userName
#[JSONField('user_name')]
public string $userName;
#[JSONField('user_age')]
public int $userAge;
}First, install the Hyperf validator:
composer require hyperf/validation
This library provides rich validation annotations, including:
Required- Required fieldInteger- IntegerNumeric- NumericBetween- Range validationMin/Max- Minimum/Maximum valueEmail- Email formatUrl- URL formatDate- Date formatDateFormat- Specified date formatBoolean- Boolean valueAlpha- Alphabetic charactersAlphaNum- Alphanumeric charactersAlphaDash- Alphanumeric characters, dashes, and underscoresImage- Image fileJson- JSON formatNullable- NullableIn- In specified valuesNotIn- Not in specified valuesRegex- Regular expressionUnique- Database uniqueExists- Database 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;
}Enable validation in the controller using the #[Valid] annotation:
#[GetMapping(path: 'query')]
public function query(#[RequestQuery] #[Valid] DemoQuery $request)
{
// Parameters have been validated
}class UserRequest
{
#[Required("Username cannot be empty")]
public string $name;
#[Between(18, 100, "Age must be between 18 and 100")]
public int $age;
}The Validation annotation supports Laravel-style validation rules:
use Hyperf\DTO\Annotation\Validation\Validation;
class ComplexRequest
{
// Use pipe separator for multiple rules
#[Validation("required|string|min:3|max:50")]
public string $username;
// Array element validation
#[Validation("integer", customKey: 'ids.*')]
public array $ids;
}Create custom validation rules by extending the BaseValidation class:
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 = 'Invalid phone number format')
{
parent::__construct($messages);
}
}Use custom validation:
use App\Validation\Phone;
class RegisterRequest
{
#[Required]
#[Phone]
public string $mobile;
}To return PHP objects in JSON-RPC services, you need to configure serialization support.
composer require symfony/serializer ^5.0|^6.0
composer require symfony/property-access ^5.0|^6.0Add to config/autoload/aspects.php:
return [
\Hyperf\DTO\Aspect\ObjectNormalizerAspect::class,
];Add to config/autoload/dependencies.php:
use Hyperf\Serializer\SerializerFactory;
use Hyperf\Serializer\Serializer;
return [
Hyperf\Contract\NormalizerInterface::class => new SerializerFactory(Serializer::class),
];If you need custom type conversion logic, you can implement your own converter:
namespace App\Convert;
use Hyperf\DTO\Type\ConvertCustom;
class CustomConvert implements ConvertCustom
{
public function convert(mixed $value): mixed
{
// Custom conversion logic
return $value;
}
}Use in DTO class:
use Hyperf\DTO\Annotation\Dto;
use Hyperf\DTO\Type\Convert;
#[Dto(Convert::SNAKE)]
class UserResponse
{
public string $name;
public int $age;
}- Create independent DTO classes for different request types
- Use meaningful class names, such as
CreateUserRequest,UpdateUserRequest - Store Request DTOs and Response DTOs separately
- Prefer built-in validation annotations for code readability
- Use
Validationannotation for complex validation - Encapsulate common validation rules as custom annotations
Validation failures throw Hyperf\Validation\ValidationException exceptions, which can be handled uniformly through an exception handler:
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: Please ensure:
- The
hyperf/validationcomponent is installed - The
#[Valid]annotation is added to the controller method parameter - Validation annotations are added to properties in the DTO class
A: Use PHPDoc or the ArrayType annotation:
/**
* @var User[]
*/
public array $users;
// Or
#[ArrayType(User::class)]
public array $users;A: No. These two annotations are mutually exclusive as they handle different request types.
A: Use the RequestFormData annotation, then retrieve the file via $this->request->file().
Issues and Pull Requests are welcome!
- Fork this repository
- Create a feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
- Hyperf - Excellent coroutine PHP framework
- JsonMapper - JSON to PHP object mapping library
If this project helps you, please give it a ⭐ Star!