New in Symfony 6.3: Mapping Request Data to Typed Objects
April 20, 2023 • Published by Javier Eguiluz
Symfony 6.3 is backed by:
Warning: This post is about an unsupported Symfony version. Some of this information may be out of date. Read the most recent Symfony Docs.
A recurring Symfony feature request during the past years has been the mapping of the incoming request data into typed objects like DTO (data transfer objects). In Symfony 6.3 we're finally introducing some new attributes to map requests to typed objects and validate them.
First, the #[MapRequestPayload]
attribute takes the data from the $_POST
PHP superglobal (via the $request->request->all()
method of the
Symfony Request object) and tries to populate a given typed object with it.
Consider the following DTO class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// ...
use Symfony\Component\Validator\Constraints as Assert;
class ProductReviewDto
{
public function __construct(
#[Assert\NotBlank]
#[Assert\Length(min: 10, max: 500)]
public readonly string $comment,
#[Assert\GreaterThanOrEqual(1)]
#[Assert\LessThanOrEqual(5)]
public readonly int $rating,
) {
}
}
In Symfony 6.3, use that class as the type-hint of some controller argument and
apply the #[MapRequestPayload]
attribute. Symfony will map the request data
into the DTO object automatically and will validate it:
1 2 3 4 5 6 7 8 9 10 11 12 13
// ...
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
class ProductApiController
{
public function __invoke(
#[MapRequestPayload] ProductReviewDto $productReview,
): Response {
// here, $productReview is a fully typed representation of the request data
}
}
That's all. About the possible error conditions when mapping data:
- Validation errors will result in HTTP
422
error responses (including a serializedConstraintViolationList
object); - Malformed data will be responded to with HTTP
400
error responses; - Unsupported deserialization formats will be responded to with HTTP
415
error responses.
Similarly, the #[MapQueryString]
takes the data from the $_GET
PHP superglobal (via the $request->query->all()
method of the
Symfony Request object) and tries to populate a given typed object with it.
Consider the following set of DTO classes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
// ...
use Symfony\Component\Validator\Constraints as Assert;
class OrdersQueryDto
{
public function __construct(
#[Assert\Valid]
public readonly ?OrdersFilterDto $filter,
#[Assert\LessThanOrEqual(500)]
public readonly int $limit = 25,
#[Assert\LessThanOrEqual(10_000)]
public readonly int $offset = 0,
) {
}
}
class OrdersFilterDto
{
public function __construct(
#[Assert\Choice(['placed', 'shipped', 'delivered'])]
public readonly ?string $status,
public readonly ?float $total,
) {
}
}
In Symfony 6.3, use that class as the type-hint of some controller argument and
apply the #[MapQueryString]
attribute. Symfony will map the request data
into the DTO object automatically and will validate it:
1 2 3 4 5 6 7 8 9 10 11 12 13
// ...
use Symfony\Component\HttpKernel\Attribute\MapQueryString;
class SearchApiController
{
public function __invoke(
#[MapQueryString] OrdersQueryDto $query,
): Response {
// here, $query is a fully typed representation of the request data
}
}
The validation logic and the error conditions of this attribute are the same as before. Also, the two attributes allow to customize both the serialization context and the class used to map the request to your objects:
1 2 3 4 5 6 7 8 9 10 11
#[MapRequestPayload(
serializationContext: ['...'],
resolver: App\...\ProductReviewRequestValueResolver
)]
ProductReviewDto $productReview
#[MapQueryString(
serializationContext: ['...'],
resolver: App\...\OrderSearchRequestValueResolver
)]
OrdersQueryDto $query
Help the Symfony project!
As with any Open-Source project, contributing code or documentation is the most common way to help, but we also have a wide range of sponsoring opportunities.
Comments are closed.
To ensure that comments stay relevant, they are closed for old posts.
I wonder if the 400 errors status could bring specific error messages to help frontend developers to understand quickly what they did wrong while having a strong validation in production.
Love it how Symfony improves release for release.
I've been waiting for something like this for a long time!
To move from the DTO to the Entity, are you using any particular pattern?