DTOs
A DTO (Data Transfer Object) is how Antares models an incoming request body. You define a readonly class, annotate it with #[Dto], add validation rules to each constructor parameter, and Antares handles the rest — decoding the body, mapping fields, validating, and injecting the populated object into your controller.
If anything fails, a 422 response is returned automatically with a field-level error map. Your controller method is never called.
Defining a DTO
Section titled “Defining a DTO”use Antares\Validation\Attributes\Dto;use Antares\Validation\Attributes\NotBlank;use Antares\Validation\Attributes\Email;use Antares\Validation\Attributes\MinLength;
#[Dto]readonly class CreateUserRequest{ public function __construct( #[NotBlank] #[MinLength(2)] public string $name,
#[NotBlank] #[Email] public string $email, ) {}}Use it in a controller by type-hinting it as a parameter:
#[Post('/users', 201)]public function store(CreateUserRequest $request): UserResponse{ return new UserResponse(id: 1, name: $request->name, email: $request->email);}Antares sees the #[Dto] annotation, decodes the request body, maps the fields, runs all validation rules, and injects the populated $request. If name is missing or email is invalid, the controller never runs and the client gets:
{ "type": "https://antares.dev/errors", "title": "Validation failed", "status": 422, "errors": { "name": ["Must be at least 2 characters"], "email": ["Must be a valid email address"] }}Default Values
Section titled “Default Values”Parameters with defaults are optional in the request body:
#[Dto]readonly class ListUsersRequest{ public function __construct( public int $page = 1, public int $perPage = 15,
#[In(['asc', 'desc'])] public string $order = 'asc', ) {}}Nested DTOs
Section titled “Nested DTOs”DTOs can nest other DTOs. The hydrator recurses into them automatically:
#[Dto]readonly class AddressRequest{ public function __construct( #[NotBlank] public string $street, #[NotBlank] public string $city, #[NotBlank] public string $country, ) {}}
#[Dto]readonly class CreateOrderRequest{ public function __construct( #[NotBlank] public string $sku, #[Min(1)] public int $quantity, public AddressRequest $address, ) {}}{ "sku": "WIDGET-42", "quantity": 3, "address": { "street": "123 Main St", "city": "Athens", "country": "GR" }}Validation errors from nested DTOs are reported under their parent field name.
Strict Mode
Section titled “Strict Mode”By default the hydrator ignores unknown fields in the request body. Add #[Strict] to throw a HydrationException → 400 Bad Request if the request sends any field not declared on the DTO:
#[Dto]#[Strict]readonly class CreateUserRequest{ public function __construct( public string $name, public string $email, ) {}}Sending "name": "John", "email": "john@example.com", "role": "admin" with #[Strict] throws a HydrationException → 400 Bad Request.
All Validation Attributes
Section titled “All Validation Attributes”Strings
Section titled “Strings”| Attribute | Description |
|---|---|
#[NotBlank] | Must not be empty |
#[MinLength(n)] | Minimum string length |
#[MaxLength(n)] | Maximum string length |
#[Email] | Valid email address |
#[Url] | Valid URL |
#[Pattern('/regex/')] | Matches regular expression |
#[Alpha] | Alphabetic characters only |
#[AlphaNumeric] | Alphanumeric characters only |
#[Numeric] | Numeric string |
#[HexColor] | Valid hex color (#fff or #ffffff) |
#[Uuid] | Valid UUID v4 |
#[Phone] | Valid phone number |
#[Ip] | Valid IP address (v4 or v6) |
#[Json] | Valid JSON string |
Numbers
Section titled “Numbers”| Attribute | Description |
|---|---|
#[Min(n)] | Minimum numeric value |
#[Max(n)] | Maximum numeric value |
#[Between(min, max)] | Value within range (inclusive) |
#[Positive] | Must be positive |
#[Negative] | Must be negative |
General
Section titled “General”| Attribute | Description |
|---|---|
#[NotNull] | Must not be null |
#[In([...])] | Must be one of the given values |
#[InEnum(MyEnum::class)] | Must be a valid enum case |
#[Size(n)] | Array must have exactly n elements |
#[ArrayOf(type)] | All array elements must be of the given type |
#[Date] | Valid date string (Y-m-d) |
#[DateTime] | Valid datetime string (Y-m-d H:i:s) |
Custom Validation Rules
Section titled “Custom Validation Rules”Implement ValidationAttribute to write your own rule:
use Antares\Validation\Attributes\ValidationAttribute;use Attribute;
#[Attribute(Attribute::TARGET_PARAMETER)]final class Slug implements ValidationAttribute{ public function validate(mixed $value): ?string { if (!preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', (string) $value)) { return 'Must be a valid slug.'; }
return null; }}Return a string error message on failure, null on pass. Then use it like any built-in attribute:
#[Dto]readonly class CreatePostRequest{ public function __construct( #[NotBlank] public string $title,
#[NotBlank] #[Slug] public string $slug, ) {}}