8000 [HttpKernel] Support backed enums in #[MapQueryParameter] · symfony/symfony@d05390c · GitHub
[go: up one dir, main page]

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

+1
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

+29-4
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

+30-2
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)
0