You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feature #49134 [HttpKernel] Add #[MapQueryParameter] to map and validate individual query parameters to controller arguments (ruudk, nicolas-grekas)
This PR was merged into the 6.3 branch.
Discussion
----------
[HttpKernel] Add `#[MapQueryParameter]` to map and validate individual query parameters to controller arguments
| Q | A
| ------------- | ---
| Branch? | 6.3
| Bug fix? |no
| New feature? | yes
| Deprecations? |no
| Tickets |
| License | MIT
| Doc PR |
We increased the PHPStan level from 8 to 9. This lead to some problems when working with query or request parameters.
For example:
```php
$firstName = $request->get('firstName');
```
Because Symfony types `Request::get()` as `mixed` there is no type safety and you have to assert everything manually.
We then learned that we shouldn't use `Request::get()` but use the explicit parameter bag like:
```php
$request->query->get('firstName');
```
This `ParameterBag` is used for `request` and `query`. It contains interesting methods like:
```php
$request->query->getAlpha('firstName') : string;
$request->query->getInt('age') : int;
```
This has the benefit that now the returned value is of a correct type.
Why aren't we being explicit by requiring the parameters and their types in the controller action instead?
Luckily Symfony has a concept called [ValueResolver](https://symfony.com/doc/current/controller/value_resolver.html).
It allows you to do dynamically alter what is injected into a controller action.
So in this PR, we introduces a new attribute: `#[MapQueryParameter]` that can be used in controller arguments.
It allows you to define which parameters your controller is using and which type they should be. For example:
```php
#[Route(path: '/', name: 'admin_dashboard')]
public function indexAction(
#[MapQueryParameter]
array $ids,
#[MapQueryParameter]
string $firstName,
#[MapQueryParameter]
bool $required,
#[MapQueryParameter]
int $age,
#[MapQueryParameter]
string $category = '',
#[MapQueryParameter]
?string $theme = null,
)
```
When requesting `/?ids[]=1&ids[]=2&firstName=Ruud&required=3&age=123` you'll get:
```
$ids = ['1', '2']
$firstName = "Ruud"
$required = false
$age = 123
$category = ''
$theme = null
```
It even supports variadic arguments like this:
```php
#[Route(path: '/', name: 'admin_dashboard')]
public function indexAction(
#[MapQueryParameter]
string ...$ids,
)
```
When requesting `/?ids[]=111&ids[]=222` the `$ids` argument will have an array with values ['111','222'].
Unit testing the controller now also becomes a bit easier, as you only have to pass the required parameters instead of constructing the `Request` object.
It's possible to use `FILTER_VALIDATE_*` consts for more precise type descriptions.
For example, this ensures that `$ids` is an array of integers:
```php
#[MapQueryParameter(filter: \FILTER_VALIDATE_INT)]
array $ids
```
And this declares a string that must match a regexp:
```php
#[MapQueryParameter(filter: \FILTER_VALIDATE_REGEXP, options: ['regexp' => '/^\w++$/'])]
string $name
```
When a filter fails, a 404 is returned.
Commits
-------
bd7c669c89 Register QueryParameterValueResolver as "controller.targeted_value_resolver"
17a8f9288f [HttpKernel] Allow injecting query parameters in controllers by typing them with `#[MapQueryParameter]` attribute
0 commit comments