Skip to content

Commit 4a5479e

Browse files
committed
[HttpKernel] Support backed enums in #[MapQueryParameter]
1 parent 80f1096 commit 4a5479e

File tree

4 files changed

+65
-3
lines changed

4 files changed

+65
-3
lines changed

UPGRADE-6.4.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ HttpFoundation
4343
HttpKernel
4444
----------
4545

46+
* Support backed enums in #[MapQueryParameter]
4647
* `BundleInterface` no longer extends `ContainerAwareInterface`
4748
* Add native return types to `TraceableEventDispatcher` and to `MergeExtensionConfigurationPass`
4849

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: 33 additions & 1 deletion
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
{
@@ -72,7 +75,17 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
7275
'int' => \FILTER_VALIDATE_INT,
7376
'float' => \FILTER_VALIDATE_FLOAT,
7477
'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'))
78+
default => \call_user_func(static function () use ($argument): int {
79+
if (is_subclass_of($argument->getType(), \BackedEnum::class)) {
80+
$backingType = (new \ReflectionEnum($argument->getType()))->getBackingType();
81+
if ('int' === $backingType->__toString()) {
82+
return \FILTER_VALIDATE_INT;
83+
} elseif ('string' === $backingType->__toString()) {
84+
return \FILTER_DEFAULT;
85+
}
86+
}
87+
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(), $argument->getType() ?? 'mixed'));
88+
})
7689
};
7790

7891
$value = filter_var($value, $attribute->filter ?? $filter, $options);
@@ -81,7 +94,24 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
8194
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name));
8295
}
8396

97+
$enumFromFunc = static function (&$value) use ($argument, $name): void {
98+
if (!\is_string($value) && !\is_int($value)) {
99+
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s": expecting an int or string, got "%s".', $name, get_debug_type($value)));
100+
}
101+
/** @var class-string<\BackedEnum> $enumType */
102+
$enumType = $argument->getType();
103+
try {
104+
$value = $enumType::from($value);
105+
} catch (\ValueError $e) {
106+
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s": "%s".', $name, $e->getMessage()), $e);
107+
}
108+
};
109+
84110
if (!\is_array($value)) {
111+
if (is_subclass_of($argument->getType(), \BackedEnum::class)) {
112+
$enumFromFunc($value);
113+
}
114+
85115
return [$value];
86116
}
87117

@@ -95,6 +125,8 @@ public function resolve(Request $request, ArgumentMetadata $argument): array
95125
throw new NotFoundHttpException(sprintf('Invalid query parameter "%s".', $name));
96126
}
97127

128+
is_subclass_of($argument->getType(), \BackedEnum::class) && array_walk($filtered, $enumFromFunc);
129+
98130
return $argument->isVariadic() ? $filtered : [$filtered];
99131
}
100132
}

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