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
Sounds great!
What about raw data like json payload?
I like it
Cool feature! There's an error in SearchApiController's snippet: MapRequestPayload is imported instead of MapQueryString.
@Cavid Huseynov You can use the 'MapRequestPayload' for that
@Valentin thanks for reporting that error. It's fixed now. Thanks.
What an amazing feature ! Hopefully, I hope it's going to be used in API platform. 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.
Nice, we'll be able to remove our good old param converters/value resolvers that were exactly doing this, thanks! Is there a way to check whether a value is null (
{"foo": null}
) or undefined ({}
) in the request, though? It may be useful when patching.Great feature, waited for it for years. ✌️ Love it how Symfony improves release for release.
Great addition, thank you!
That's a very nice feature, congrats!
Great feature! I've been waiting for something like this for a long time!
Nice!
Great timing. Can use it in a new project I am currently working in 😻
It will be usable, It will replace Form component when we write API
This is one feature I was waiting for especially replacing form for api development development.
Very nice!!
To move from the DTO to the Entity, are you using any particular pattern?