Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[WIP] Request validator #49002

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

Closed
wants to merge 4 commits into from
Closed

[WIP] Request validator #49002

wants to merge 4 commits into from

Conversation

y4roc
Copy link

@y4roc y4roc commented Jan 16, 2023

Q A
Branch? 6.3
Bug fix? no
New feature? yes
Deprecations? no
Tickets Fix #48908
License MIT
Doc PR symfony/symfony-docs#17786

It will add an attribute to validate and transfer incoming request with to a dto.

Example usage:

class PostUserRequest {
    #[Assert\NotBlank]
    #[Assert\Type('string')]
    public $username;

    #[Assert\NotBlank]
    #[Assert\Email]
    #[Assert\Type('string')]
    public $email;
}

Controller:

class PostUserAction extends AbstractController {
    #[RequestValidator(class: PostUserRequest::class)]
    #[Route(path: '/user', methods: ['POST'])]
    public function __invoke(PostUserRequest $request) {
        ...
    }
}

@OskarStark
Copy link
Contributor

OskarStark commented Jan 17, 2023

You should associate your commiter email address with your Github account

@OskarStark
Copy link
Contributor

Can you provide at least one test for the happy path? It makes it easier to discover this feature, thanks.

@y4roc y4roc changed the title Draft: Request validator [WIP] Request validator Jan 18, 2023
@y4roc
Copy link
Author

y4roc commented Jan 18, 2023

I am in the process of writing tests.

@Koc
Copy link
Contributor

Koc commented Jan 23, 2023

What about moving attribute to argument (this allow to omit class definition) and split attributes per source?

public function __invoke(
    #[MappedBody] PostUserRequest $request, // $request->getContent() or $request->request->all()
    #[MappedQueryString] PostUsersQueryString $queryString, // $request->query->all()
    string $_locale, // request attribute
) {

BTW we already can access Request Attributes without any attributes, do we really need add additional way to fetch them?

@y4roc
Copy link
Author

y4roc commented Jan 23, 2023

@Koc I find the idea with the argument attributes very good.
If necessary, you could adjust the order of filling in the method attributes.

To the second point. The attribute should not serve to access the request attributes, but to validate them in advance. The passing of the filled object is only a small addition and not my main focus.

@Koc
Copy link
Contributor

Koc commented Jan 24, 2023

for sure, MappedBody/MappedQueryString can do validation under the hood 👍

@y4roc
Copy link
Author

y4roc commented Jan 24, 2023

Can you share a link to the documentation of #MappedBody?

@Koc
Copy link
Contributor

Koc commented Jan 28, 2023

This was not opensourced yet :). Now I've opened PR with that attributes

@nicolas-grekas
Copy link
Member

Closing in favor of #49138, thanks for pushing this idea!

nicolas-grekas added a commit that referenced this pull request Apr 14, 2023
…and `#[MapQueryString]` to map Request input to typed objects (Koc)

This PR was merged into the 6.3 branch.

Discussion
----------

[HttpKernel] Create Attributes `#[MapRequestPayload]` and `#[MapQueryString]` to map Request input to typed objects

| Q             | A
| ------------- | ---
| Branch?       | 6.3
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | #36037, #36093, #45628, #47425,  #49002, #49134
| License       | MIT
| Doc PR        | TBD

Yet another variation of how we can map raw Request data to typed objects with validation. We can even build OpenApi Specification based on this DTO classes using [NelmioApiDocBundle](https://github.com/nelmio/NelmioApiDocBundle).

## Usage Example 🔧
### `#[MapRequestPayload]`

```php
class PostProductReviewPayload
{
    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,
    ) {
    }
}

class PostJsonApiController
{
    public function __invoke(
        #[MapRequestPayload] PostProductReviewPayload $payload,
    ): Response {
        // $payload is validated and fully typed representation of the request body $request->getContent()
        //  or $request->request->all()
    }
}
```

### `#[MapQueryString]`

```php
class GetOrdersQuery
{
    public function __construct(
        #[Assert\Valid]
        public readonly ?GetOrdersFilter $filter,
        #[Assert\LessThanOrEqual(500)]
        public readonly int $limit = 25,
        #[Assert\LessThanOrEqual(10_000)]
        public readonly int $offset = 0,
    ) {
    }
}

class GetOrdersFilter
{
    public function __construct(
        #[Assert\Choice(['placed', 'shipped', 'delivered'])]
        public readonly ?string $status,
        public readonly ?float $total,
    ) {
    }
}

class GetApiController
{
    public function __invoke(
        #[MapQueryString] GetOrdersQuery $query,
    ): Response {
        // $query is validated and fully typed representation of the query string $request->query->all()
    }
}
```

### Exception handling 💥
- Validation errors will result in an HTTP 422 response, accompanied by a serialized `ConstraintViolationList`.
- Malformed data will be responded to with an HTTP 400 error.
- Unsupported deserialization formats will be responded to with an HTTP 415 error.

## Comparison to another implementations 📑

Differences to #49002:
- separate Attributes for explicit definition of the used source
- no need to define which class use to map because Attributes already associated with typed argument
- used ArgumentValueResolver mechanism instead of Subscribers
- functional tests

Differences to #49134:
- it is possible to map whole query string to object, not per parameter
- there is validation of the mapped object
- supports both `$request->getContent()` and `$request->request->all()` mapping
- functional tests

Differences to #45628:
- separate Attributes for explicit definition of the used source
- supports `$request->request->all()` and `$request->query->all()` mapping
- Exception handling opt-in
- functional tests

## Bonus part 🎁
- Extracted `UnsupportedFormatException` which thrown when there is no decoder for a given format

Commits
-------

d987093 [HttpKernel] Create Attributes `#[MapRequestPayload]` and `#[MapQueryString]` to map Request input to typed objects
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Router] Validate Request in Router
5 participants
Morty Proxy This is a proxified and sanitized view of the page, visit original site.