Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7733558

Browse files
committed
feature #54153 [HttpKernel] allow boolean argument support for MapQueryString (Jean-Beru)
This PR was squashed before being merged into the 7.1 branch. Discussion ---------- [HttpKernel] allow boolean argument support for MapQueryString | Q | A | ------------- | --- | Branch? | 7.1 | Bug fix? | no | New feature? | yes | Deprecations? | no | Issues | Fix #53616 | License | MIT ~~Add a `$filters` argument to the `MapQueryString` attribute to allow filtering parameters using `filter_var` function. It will be useful to cast "false", "0", "no" or "off" to `false` before denormalizing the request data.~~ **EDIT** Add a `AbstractNormalizer::FILTER_BOOL` context option to cast to a boolean using the `filter_var` function with the `\FILTER_VALIDATE_BOOL` filter (see https://www.php.net/manual/fr/filter.filters.validate.php). `MapQueryString` will use this context option when denormalizing the query string. `MapPayload` also but only with data coming from a form. See the relative comment: symfony/symfony#54153 (comment) Example: ```php final readonly class SearchDto { public function __construct(public ?bool $restricted = null) { } } final class MyController { #[Route(name: 'search', path: '/search')] public function __invoke(#[MapQueryString] SearchDto $search): Response { // /search?restricted= // "true", "1", "yes" and "on" will be cast to true // "false", "0", "no", "off" and "" will be cast to false // other will be cast to null } } ``` Commits ------- 5c20295662 [HttpKernel] allow boolean argument support for MapQueryString
2 parents a53d832 + dcde688 commit 7733558

File tree

2 files changed

+91
-2
lines changed

2 files changed

+91
-2
lines changed

Controller/ArgumentResolver/RequestPayloadValueResolver.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ private function mapQueryString(Request $request, string $type, MapQueryString $
166166
return null;
167167
}
168168

169-
return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
169+
return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ['filter_bool' => true]);
170170
}
171171

172172
private function mapRequestPayload(Request $request, string $type, MapRequestPayload $attribute): ?object
@@ -180,7 +180,7 @@ private function mapRequestPayload(Request $request, string $type, MapRequestPay
180180
}
181181

182182
if ($data = $request->request->all()) {
183-
return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE);
183+
return $this->serializer->denormalize($data, $type, null, $attribute->serializationContext + self::CONTEXT_DENORMALIZE + ('form' === $format ? ['filter_bool' => true] : []));
184184
}
185185

186186
if ('' === $data = $request->getContent()) {

Tests/Controller/ArgumentResolver/RequestPayloadValueResolverTest.php

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -722,6 +722,88 @@ public function testRequestPayloadValidationErrorCustomStatusCode()
722722
$this->assertSame('This value should be of type string.', $validationFailedException->getViolations()[0]->getMessage());
723723
}
724724
}
725+
726+
/**
727+
* @dataProvider provideBoolArgument
728+
*/
729+
public function testBoolArgumentInQueryString(mixed $expectedValue, ?string $parameterValue)
730+
{
731+
$serializer = new Serializer([new ObjectNormalizer()]);
732+
$validator = $this->createMock(ValidatorInterface::class);
733+
$resolver = new RequestPayloadValueResolver($serializer, $validator);
734+
735+
$argument = new ArgumentMetadata('filtered', ObjectWithBoolArgument::class, false, false, null, false, [
736+
MapQueryString::class => new MapQueryString(),
737+
]);
738+
$request = Request::create('/', 'GET', ['value' => $parameterValue]);
739+
740+
$kernel = $this->createMock(HttpKernelInterface::class);
741+
$arguments = $resolver->resolve($request, $argument);
742+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
743+
744+
$resolver->onKernelControllerArguments($event);
745+
746+
$this->assertSame($expectedValue, $event->getArguments()[0]->value);
747+
}
748+
749+
/**
750+
* @dataProvider provideBoolArgument
751+
*/
752+
public function testBoolArgumentInBody(mixed $expectedValue, ?string $parameterValue)
753+
{
754+
$serializer = new Serializer([new ObjectNormalizer()]);
755+
$validator = $this->createMock(ValidatorInterface::class);
756+
$resolver = new RequestPayloadValueResolver($serializer, $validator);
757+
758+
$argument = new ArgumentMetadata('filtered', ObjectWithBoolArgument::class, false, false, null, false, [
759+
MapRequestPayload::class => new MapRequestPayload(),
760+
]);
761+
$request = Request::create('/', 'POST', ['value' => $parameterValue], server: ['CONTENT_TYPE' => 'multipart/form-data']);
762+
763+
$kernel = $this->createMock(HttpKernelInterface::class);
764+
$arguments = $resolver->resolve($request, $argument);
765+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
766+
767+
$resolver->onKernelControllerArguments($event);
768+
769+
$this->assertSame($expectedValue, $event->getArguments()[0]->value);
770+
}
771+
772+
public static function provideBoolArgument()
773+
{
774+
yield 'default value' => [null, null];
775+
yield '"0"' => [false, '0'];
776+
yield '"false"' => [false, 'false'];
777+
yield '"no"' => [false, 'no'];
778+
yield '"off"' => [false, 'off'];
779+
yield '"1"' => [true, '1'];
780+
yield '"true"' => [true, 'true'];
781+
yield '"yes"' => [true, 'yes'];
782+
yield '"on"' => [true, 'on'];
783+
}
784+
785+
/**
786+
* Boolean filtering must be disabled for content types other than form data.
787+
*/
788+
public function testBoolArgumentInJsonBody()
789+
{
790+
$serializer = new Serializer([new ObjectNormalizer()]);
791+
$validator = $this->createMock(ValidatorInterface::class);
792+
$resolver = new RequestPayloadValueResolver($serializer, $validator);
793+
794+
$argument = new ArgumentMetadata('filtered', ObjectWithBoolArgument::class, false, false, null, false, [
795+
MapRequestPayload::class => new MapRequestPayload(),
796+
]);
797+
$request = Request::create('/', 'POST', ['value' => 'off'], server: ['CONTENT_TYPE' => 'application/json']);
798+
799+
$kernel = $this->createMock(HttpKernelInterface::class);
800+
$arguments = $resolver->resolve($request, $argument);
801+
$event = new ControllerArgumentsEvent($kernel, function () {}, $arguments, $request, HttpKernelInterface::MAIN_REQUEST);
802+
803+
$resolver->onKernelControllerArguments($event);
804+
805+
$this->assertTrue($event->getArguments()[0]->value);
806+
}
725807
}
726808

727809
class RequestPayload
@@ -765,3 +847,10 @@ public function getPassword(): string
765847
return $this->password;
766848
}
767849
}
850+
851+
class ObjectWithBoolArgument
852+
{
853+
public function __construct(public readonly ?bool $value = null)
854+
{
855+
}
856+
}

0 commit comments

Comments
 (0)