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

Skip to content

Commit 5b73e6b

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 50f11c6 + a9853ac commit 5b73e6b

File tree

8 files changed

+118
-8
lines changed

8 files changed

+118
-8
lines changed

CHANGELOG.md

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

77
* Add `DateTimeNormalizer::CAST_KEY` context option
88
* Add `Default` and "class name" default groups
9+
* Add `AbstractNormalizer::FILTER_BOOL` context option
910

1011
7.0
1112
---

Normalizer/AbstractNormalizer.php

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ abstract class AbstractNormalizer implements NormalizerInterface, DenormalizerIn
118118
*/
119119
public const REQUIRE_ALL_PROPERTIES = 'require_all_properties';
120120

121+
/**
122+
* Flag to control whether a non-boolean value should be filtered using the
123+
* filter_var function with the {@see https://www.php.net/manual/fr/filter.filters.validate.php}
124+
* \FILTER_VALIDATE_BOOL filter before casting it to a boolean.
125+
*
126+
* "0", "false", "off", "no" and "" will be cast to false.
127+
* "1", "true", "on" and "yes" will be cast to true.
128+
*/
129+
public const FILTER_BOOL = 'filter_bool';
130+
121131
/**
122132
* @internal
123133
*/
@@ -436,12 +446,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
436446
unset($context['has_constructor']);
437447

438448
if (!$reflectionClass->isInstantiable()) {
439-
throw NotNormalizableValueException::createForUnexpectedDataType(
440-
sprintf('Failed to create object because the class "%s" is not instantiable.', $class),
441-
$data,
442-
['unknown'],
443-
$context['deserialization_path'] ?? null
444-
);
449+
throw NotNormalizableValueException::createForUnexpectedDataType(sprintf('Failed to create object because the class "%s" is not instantiable.', $class), $data, ['unknown'], $context['deserialization_path'] ?? null);
445450
}
446451

447452
return new $class();
@@ -473,7 +478,9 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
473478
return null;
474479
}
475480

476-
return $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context);
481+
$parameterData = $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context);
482+
483+
return $this->applyFilterBool($parameter, $parameterData, $context);
477484
}
478485

479486
/**
@@ -524,6 +531,19 @@ final protected function applyCallbacks(mixed $value, object|string $object, str
524531
return $callback ? $callback($value, $object, $attribute, $format, $context) : $value;
525532
}
526533

534+
final protected function applyFilterBool(\ReflectionParameter $parameter, mixed $value, array $context): mixed
535+
{
536+
if (!($context[self::FILTER_BOOL] ?? false)) {
537+
return $value;
538+
}
539+
540+
if (!($parameterType = $parameter->getType()) instanceof \ReflectionNamedType || 'bool' !== $parameterType->getName()) {
541+
return $value;
542+
}
543+
544+
return filter_var($value, \FILTER_VALIDATE_BOOL, \FILTER_NULL_ON_FAILURE) ?? $value;
545+
}
546+
527547
/**
528548
* Computes the normalization context merged with current one. Metadata always wins over global context, as more specific.
529549
*

Normalizer/AbstractObjectNormalizer.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,9 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
601601

602602
$parameterData = $this->validateAndDenormalize($types, $class->getName(), $parameterName, $parameterData, $format, $context);
603603

604-
return $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context);
604+
$parameterData = $this->applyCallbacks($parameterData, $class->getName(), $parameterName, $format, $context);
605+
606+
return $this->applyFilterBool($parameter, $parameterData, $context);
605607
}
606608

607609
/**
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Normalizer\Features;
13+
14+
class FilterBoolObject
15+
{
16+
public function __construct(public ?bool $value)
17+
{
18+
}
19+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Serializer\Tests\Normalizer\Features;
13+
14+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
15+
16+
/**
17+
* Test AbstractNormalizer::FILTER_BOOL.
18+
*/
19+
trait FilterBoolTestTrait
20+
{
21+
abstract protected function getNormalizerForFilterBool(): DenormalizerInterface;
22+
23+
/**
24+
* @dataProvider provideObjectWithBoolArguments
25+
*/
26+
public function testObjectWithBoolArguments(?bool $expectedValue, ?string $parameterValue)
27+
{
28+
$normalizer = $this->getNormalizerForFilterBool();
29+
30+
$dummy = $normalizer->denormalize(['value' => $parameterValue], FilterBoolObject::class, context: ['filter_bool' => true]);
31+
32+
$this->assertSame($expectedValue, $dummy->value);
33+
}
34+
35+
public static function provideObjectWithBoolArguments()
36+
{
37+
yield 'default value' => [null, null];
38+
yield '0' => [false, '0'];
39+
yield 'false' => [false, 'false'];
40+
yield 'no' => [false, 'no'];
41+
yield 'off' => [false, 'off'];
42+
yield '1' => [true, '1'];
43+
yield 'true' => [true, 'true'];
44+
yield 'yes' => [true, 'yes'];
45+
yield 'on' => [true, 'on'];
46+
}
47+
}

Tests/Normalizer/GetSetMethodNormalizerTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksTestTrait;
4040
use Symfony\Component\Serializer\Tests\Normalizer\Features\CircularReferenceTestTrait;
4141
use Symfony\Component\Serializer\Tests\Normalizer\Features\ConstructorArgumentsTestTrait;
42+
use Symfony\Component\Serializer\Tests\Normalizer\Features\FilterBoolTestTrait;
4243
use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait;
4344
use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait;
4445
use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait;
@@ -53,6 +54,7 @@ class GetSetMethodNormalizerTest extends TestCase
5354
use CallbacksTestTrait;
5455
use CircularReferenceTestTrait;
5556
use ConstructorArgumentsTestTrait;
57+
use FilterBoolTestTrait;
5658
use GroupsTestTrait;
5759
use IgnoredAttributesTestTrait;
5860
use MaxDepthTestTrait;
@@ -279,6 +281,11 @@ protected function getDenormalizerForGroups(): GetSetMethodNormalizer
279281
return new GetSetMethodNormalizer($classMetadataFactory);
280282
}
281283

284+
protected function getNormalizerForFilterBool(): GetSetMethodNormalizer
285+
{
286+
return new GetSetMethodNormalizer();
287+
}
288+
282289
public function testGroupsNormalizeWithNameConverter()
283290
{
284291
$classMetadataFactory = new ClassMetadataFactory(new AttributeLoader());

Tests/Normalizer/ObjectNormalizerTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
use Symfony\Component\Serializer\Tests\Normalizer\Features\CircularReferenceTestTrait;
5151
use Symfony\Component\Serializer\Tests\Normalizer\Features\ConstructorArgumentsTestTrait;
5252
use Symfony\Component\Serializer\Tests\Normalizer\Features\ContextMetadataTestTrait;
53+
use Symfony\Component\Serializer\Tests\Normalizer\Features\FilterBoolTestTrait;
5354
use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait;
5455
use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait;
5556
use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait;
@@ -72,6 +73,7 @@ class ObjectNormalizerTest extends TestCase
7273
use CircularReferenceTestTrait;
7374
use ConstructorArgumentsTestTrait;
7475
use ContextMetadataTestTrait;
76+
use FilterBoolTestTrait;
7577
use GroupsTestTrait;
7678
use IgnoredAttributesTestTrait;
7779
use MaxDepthTestTrait;
@@ -345,6 +347,11 @@ protected function getDenormalizerForAttributes(): ObjectNormalizer
345347
return $normalizer;
346348
}
347349

350+
protected function getNormalizerForFilterBool(): ObjectNormalizer
351+
{
352+
return new ObjectNormalizer();
353+
}
354+
348355
public function testAttributesContextDenormalizeConstructor()
349356
{
350357
$normalizer = new ObjectNormalizer(null, null, null, new ReflectionExtractor());

Tests/Normalizer/PropertyNormalizerTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use Symfony\Component\Serializer\Tests\Normalizer\Features\CallbacksTestTrait;
3939
use Symfony\Component\Serializer\Tests\Normalizer\Features\CircularReferenceTestTrait;
4040
use Symfony\Component\Serializer\Tests\Normalizer\Features\ConstructorArgumentsTestTrait;
41+
use Symfony\Component\Serializer\Tests\Normalizer\Features\FilterBoolTestTrait;
4142
use Symfony\Component\Serializer\Tests\Normalizer\Features\GroupsTestTrait;
4243
use Symfony\Component\Serializer\Tests\Normalizer\Features\IgnoredAttributesTestTrait;
4344
use Symfony\Component\Serializer\Tests\Normalizer\Features\MaxDepthTestTrait;
@@ -52,6 +53,7 @@ class PropertyNormalizerTest extends TestCase
5253
use CallbacksTestTrait;
5354
use CircularReferenceTestTrait;
5455
use ConstructorArgumentsTestTrait;
56+
use FilterBoolTestTrait;
5557
use GroupsTestTrait;
5658
use IgnoredAttributesTestTrait;
5759
use MaxDepthTestTrait;
@@ -259,6 +261,11 @@ protected function getSelfReferencingModel()
259261
return new PropertyCircularReferenceDummy();
260262
}
261263

264+
protected function getNormalizerForFilterBool(): PropertyNormalizer
265+
{
266+
return new PropertyNormalizer();
267+
}
268+
262269
public function testSiblingReference()
263270
{
264271
$serializer = new Serializer([$this->normalizer]);

0 commit comments

Comments
 (0)