Skip to content

Commit d05390c

Browse files
andersmateusznicolas-grekas
authored andcommitted
[HttpKernel] Support backed enums in #[MapQueryParameter]
1 parent 80f1096 commit d05390c

File tree

3 files changed

+60
-6
lines changed

3 files changed

+60
-6
lines changed

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
6.4
55
---
66

7+
* Support backed enums in #[MapQueryParameter]
78
* `BundleInterface` no longer extends `ContainerAwareInterface`
89
* Add optional `$className` parameter to `ControllerEvent::getAttributes()`
910
* Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass`

src/Symfony/Component/HttpKernel/Controller/ArgumentResolver/QueryParameterValueResolver.php

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,11 @@
1818
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1919

2020
/**
21+
* Resolve arguments of type: array, string, int, float, bool, \BackedEnum from query parameters.
22+
*
2123
* @author Ruud Kamphuis <ruud@ticketswap.com>
2224
* @author Nicolas Grekas <p@tchwork.com>
25+
* @author Mateusz Anders <anders_mateusz@outlook.com>
2326
*/
2427
final class QueryParameterValueResolver implements ValueResolverInterface
2528
{
@@ -39,8 +42,9 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
3942
}
4043

4144
$value = $request->query->all()[$name];
45+
$type = $argument->getType();
4246

43-
if (null === $attribute->filter && 'array' === $argument->getType()) {
47+
if (null === $attribute->filter && 'array' === $type) {
4448
if (!$argument->isVariadic()) {
4549
return [(array) $value];
4650
}
@@ -59,20 +63,25 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
5963
'options' => $attribute->options,
6064
];
6165

62-
if ('array' === $argument->getType() || $argument->isVariadic()) {
66+
if ('array' === $type || $argument->isVariadic()) {
6367
$value = (array) $value;
6468
$options['flags'] |= \FILTER_REQUIRE_ARRAY;
6569
} else {
6670
$options['flags'] |= \FILTER_REQUIRE_SCALAR;
6771
}
6872

69-
$filter = match ($argument->getType()) {
73+
$enumType = null;
74+
$filter = match ($type) {
7075
'array' => \FILTER_DEFAULT,
7176
'string' => \FILTER_DEFAULT,
7277
'int' => \FILTER_VALIDATE_INT,
7378
'float' => \FILTER_VALIDATE_FLOAT,
7479
'bool' => \FILTER_VALIDATE_BOOL,
75-
default => throw new \LogicException(sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float or bool should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $argument->getType() ?? 'mixed'))
80+
default => match ($enumType = is_subclass_of($type, \BackedEnum::class) ? (new \ReflectionEnum($type))->getBackingType()->getName() : null) {
81+
'int' => \FILTER_VALIDATE_INT,
82+
'string' => \FILTER_DEFAULT,
83+
default => throw new \LogicException(sprintf('#[MapQueryParameter] cannot be used on controller argument "%s$%s" of type "%s"; one of array, string, int, float, bool or \BackedEnum should be used.', $argument->isVariadic() ? '...' : '', $argument->getName(), $type ?? 'mixed')),
84+
}
7685
};
7786

7887
$value = filter_var($value, $attribute->filter ?? $filter, $options);
@@ -81,6 +90,22 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
8190
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name));
8291
}
8392

93+
if (null !== $enumType && null !== $value) {
94+
$enumFrom = static function ($value) use ($type, $name) {
95+
if (null !== $value && !\is_string($value) && !\is_int($value)) {
96+
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s": expecting an int or string, got "%s".', $name, get_debug_type($value)));
97+
}
98+
99+
try {
100+
return null === $value ? null : $type::from($value);
101+
} catch (\ValueError $e) {
102+
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s": '.$e->getMessage().'.', $name), $e);
103+
}
104+
};
105+
106+
$value = \is_array($value) ? array_map($enumFrom, $value) : $enumFrom($value);
107+
}
108+
84109
if (!\is_array($value)) {
85110
return [$value];
86111
}

src/Symfony/Component/HttpKernel/Tests/Controller/ArgumentResolver/QueryParameterValueResolverTest.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
1919
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
2020
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
21+
use Symfony\Component\HttpKernel\Tests\Fixtures\Suit;
2122

2223
class QueryParameterValueResolverTest extends TestCase
2324
{
@@ -176,6 +177,33 @@ public static function provideTestResolve(): iterable
176177
'Invalid query parameter "isVerified".',
177178
];
178179

180+
yield 'parameter found and backing value' => [
181+
Request::create('/', 'GET', ['suit' => 'H']),
182+
new ArgumentMetadata('suit', Suit::class, false, false, false, attributes: [new MapQueryParameter()]),
183+
[Suit::Hearts],
184+
null,
185+
];
186+
yield 'parameter found and backing value variadic' => [
187+
Request::create('/', 'GET', ['suit' => ['H', 'D']]),
188+
new ArgumentMetadata('suit', Suit::class, true, false, false, attributes: [new MapQueryParameter()]),
189+
[Suit::Hearts, Suit::Diamonds],
190+
null,
191+
];
192+
yield 'parameter found and backing type not int nor string' => [
193+
Request::create('/', 'GET', ['suit' => 1]),
194+
new ArgumentMetadata('suit', Suit::class, false, false, false, attributes: [new MapQueryParameter(filter: \FILTER_VALIDATE_BOOL)]),
195+
[],
196+
NotFoundHttpException::class,
197+
'Invalid query parameter "suit": expecting an int or string, got "bool".',
198+
];
199+
yield 'parameter found and backing type not valid backing value for enum' => [
200+
Request::create('/', 'GET', ['suit' => 10.99]),
201+
new ArgumentMetadata('suit', Suit::class, false, false, false, attributes: [new MapQueryParameter()]),
202+
[],
203+
NotFoundHttpException::class,
204+
'Invalid query parameter "suit":',
205+
];
206+
179207
yield 'parameter not found but nullable' => [
180208
Request::create('/', 'GET'),
181209
new ArgumentMetadata('firstName', 'string', false, false, false, true, [new MapQueryParameter()]),
@@ -203,14 +231,14 @@ public static function provideTestResolve(): iterable
203231
new ArgumentMetadata('standardClass', \stdClass::class, false, false, false, attributes: [new MapQueryParameter()]),
204232
[],
205233
\LogicException::class,
206-
'#[MapQueryParameter] cannot be used on controller argument "$standardClass" of type "stdClass"; one of array, string, int, float or bool should be used.',
234+
'#[MapQueryParameter] cannot be used on controller argument "$standardClass" of type "stdClass"; one of array, string, int, float, bool or \BackedEnum should be used.',
207235
];
208236
yield 'unsupported type variadic' => [
209237
Request::create('/', 'GET', ['standardClass' => 'test']),
210238
new ArgumentMetadata('standardClass', \stdClass::class, true, false, false, attributes: [new MapQueryParameter()]),
211239
[],
212240
\LogicException::class,
213-
'#[MapQueryParameter] cannot be used on controller argument "...$standardClass" of type "stdClass"; one of array, string, int, float or bool should be used.',
241+
'#[MapQueryParameter] cannot be used on controller argument "...$standardClass" of type "stdClass"; one of array, string, int, float, bool or \BackedEnum should be used.',
214242
];
215243
}
216244

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy