Quantcast
Channel: Symfony Blog
Viewing all articles
Browse latest Browse all 3133

New in Symfony 6.3: Mapping Request Data to Typed Objects

$
0
0
Konstantin Myakshin

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

// ...
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 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 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:

// ...
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;

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.

Viewing all articles
Browse latest Browse all 3133

Trending Articles