Skip to content

RFC: Extract and Extend Request Mapping Logic Beyond Controllers #60312

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
sfmok opened this issue May 1, 2025 · 3 comments
Open

RFC: Extract and Extend Request Mapping Logic Beyond Controllers #60312

sfmok opened this issue May 1, 2025 · 3 comments
Labels
RFC RFC = Request For Comments (proposals about features that you want to be discussed)

Comments

@sfmok
Copy link
Contributor

sfmok commented May 1, 2025

Description

Motivation

Symfony provides attributes like #[MapRequestPayload], #[MapQueryParameter], and #[MapUploadedFile] to conveniently map request data to typed objects (DTOs) in controller actions. These attributes are resolved using Symfony’s argument value resolvers and provide a powerful declarative experience — including automatic validation, format handling, and integration with the Serializer and Validator components.

However, this functionality is currently limited to controller arguments. In many real-world applications, developers frequently need to map and validate request data outside of controllers — for example:

  • In custom authenticators
  • In event listeners or middleware
  • In reusable services handling Request objects

Currently, developers must reimplement mapping and validation logic manually in these contexts, leading to duplicated effort, inconsistent behavior, and poor DX (developer experience).

Goal

Enable the same mapping and validation behavior available via #[MapRequestPayload], #[MapQueryParameter], and #[MapUploadedFile] in any Symfony service, not just controller actions.

Proposal

  1. Extract the core logic of the existing attribute-based mapping system into reusable internal services:

    • PayloadMapper — handles deserialization of JSON/XML/etc. from the request body
    • QueryParameterMapper — maps query string data into DTOs
    • UploadedFileMapper — extracts and maps uploaded files

These will encapsulate the transformation logic without coupling to the controller resolver system.

  1. Introduce a high-level public API: RequestDataMapper
    This service:

    • Combines mapping and validation
    • Uses the mappers above
    • Reproduces the exact behavior of controller argument resolution
    • Throws ValidationFailedException on constraint violations
    • Designed for use in custom authenticators, listeners, services, etc.

Example usage:

$dto = $this->requestDataMapper->mapAndValidatePayload($request, LoginRequest::class);

Design
Low-level mappers (internal services)

  • PayloadMapper::map(Request $request, string $class): object
  • QueryParameterMapper::map(Request $request, string $class): object
  • UploadedFileMapper::map(Request $request, string $field): ?UploadedFile

These services handle extraction and transformation only. No validation logic.

High-level public service

final class RequestDataMapper
{
    public function __construct(
        private PayloadMapper $payloadMapper,
        private QueryParameterMapper $queryMapper,
        private UploadedFileMapper $fileMapper,
        private ValidatorInterface $validator
    ) {}

    public function mapAndValidatePayload(Request $request, string $class, array $serializerContext = [], array|string|null $validationGroups = null, ?string $format = null): object;

    public function mapAndValidateQuery(Request $request, string $class): object;

    public function mapUploadedFile(Request $request, string $field): ?UploadedFile;
}

Integration

  • No changes to controller attribute behavior. The #[MapRequestPayload] and other attributes will continue to use the current value resolver mechanism.
  • Internally, these resolvers will be refactored to delegate to the new mappers, avoiding duplicated logic and ensuring consistency.
  • Developers can inject RequestDataMapper into any service via autowiring.

Backward Compatibility

  • This change is fully backward-compatible.
  • All existing controller attribute behavior remains unchanged.
  • The new mappers and RequestDataMapper are additive and opt-in.

Implementation Plan
If accepted, I propose to:

  1. Extract existing logic from the controller value resolvers into three private mappers:

    • PayloadMapper
    • QueryParameterMapper
    • UploadedFileMapper
  2. Introduce a new public service RequestDataMapper that:

    • Uses the above
    • Handles validation
    • Mirrors controller behavior
  3. Update the current value resolvers to delegate to the new services

  4. Add functional and unit tests for:

    • Each mapper individually
    • Combined RequestDataMapper usage
    • Exception handling and validation
  5. Update Symfony documentation (if needed) with examples for RequestDataMapper.

Conclusion
This RFC proposes a clean, fully backward-compatible enhancement to Symfony's request handling model. It extracts and elevates the powerful attribute-based mapping logic into reusable, injectable services — aligning with Symfony’s philosophy of modularity and DX excellence.

It closes a common gap experienced by developers working with authenticators, listeners, and service-level request data access.

Example

Use Cases

  1. Authenticators
public function authenticate(Request $request): PassportInterface
{
    $dto = $this->requestDataMapper->mapAndValidatePayload($request, LoginRequest::class);

    return new Passport(
        new UserBadge($dto->username),
        new PasswordCredentials($dto->password)
    );
}
  1. Event Subscribers
public function onKernelRequest(RequestEvent $event): void
{
    $filter = $this->requestDataMapper->mapAndValidateQuery($event->getRequest(), FilterDto::class);
    // ...
}
  1. Services
$dto = $this->requestDataMapper->mapAndValidatePayload($request, SearchCriteria::class);
@xabbuh xabbuh added the RFC RFC = Request For Comments (proposals about features that you want to be discussed) label May 1, 2025
@valtzu
Copy link
Contributor

valtzu commented May 4, 2025

Duplicate of #53915 ?

There is also a PR to introduce ArgumentResolver component: #59794

@mdeboer
Copy link
Contributor

mdeboer commented May 5, 2025

Duplicate of #53915 ?

There is also a PR to introduce ArgumentResolver component: #59794

Imho not a duplicate. This RFC argues that the functionality of mapping requests should not be (just a) part of the argument resolver, but as different components. Those components have nothing to do with argument resolving.

Allowing to call argument resolvers outside of controllers does not solve the architectural problem imho.

@sfmok
Copy link
Contributor Author

sfmok commented May 6, 2025

Duplicate of #53915 ?

There is also a PR to introduce ArgumentResolver component: #59794

Duplicate of #53915 ?
There is also a PR to introduce ArgumentResolver component: #59794

Imho not a duplicate. This RFC argues that the functionality of mapping requests should not be (just a) part of the argument resolver, but as different components. Those components have nothing to do with argument resolving.

Allowing to call argument resolvers outside of controllers does not solve the architectural problem imho.

Thanks for the input! I agree this is not a duplicate of #53915 or #59794. Those focus on making argument resolvers more flexible or reusable, but this RFC proposes a dedicated component for mapping and validating request data independently of argument resolution. The goal is to support use cases like authenticators or event listeners, where simulating controller callables just to deserialize and validate DTOs is awkward. This is about separating concerns and enabling cleaner, more explicit application architecture.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
RFC RFC = Request For Comments (proposals about features that you want to be discussed)
Projects
None yet
Development

No branches or pull requests

4 participants