New in Symfony 6.3: Mapping Request Data to Typed Objects

Contributed by
Konstantin Myakshin

in #49138.

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:

// …
use SymfonyComponentValidatorConstraints as Assert;

class ProductReviewDto
{
public function __construct(
#[AssertNotBlank]
#[AssertLength(min: 10, max: 500)]
public readonly string $comment,

#[AssertGreaterThanOrEqual(1)]
#[AssertLessThanOrEqual(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:

// …
use SymfonyComponentHttpKernelAttributeMapRequestPayload;

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
serialized ConstraintViolationList 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:

// …
use SymfonyComponentValidatorConstraints as Assert;

class OrdersQueryDto
{
public function __construct(
#[AssertValid]
public readonly ?OrdersFilterDto $filter,

#[AssertLessThanOrEqual(500)]
public readonly int $limit = 25,

#[AssertLessThanOrEqual(10_000)]
public readonly int $offset = 0,
) {
}
}

class OrdersFilterDto
{
public function __construct(
#[AssertChoice([‚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:

// …
use SymfonyComponentHttpKernelAttributeMapRequestPayload;

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, in both cases you can customize both the context and the class
used to map the request to your objects:

#[MapRequestPayload(
context: [‚…‘],
resolver: App…ProductReviewRequestValueResolver
)]
ProductReviewDto $productReview

#[MapQueryString(
context: [‚…‘],
resolver: App…OrderSearchRequestValueResolver
)]
OrdersQueryDto $query

Sponsor the Symfony project.

Symfony Blog

Read More

Generated by Feedzy