diff --git a/src/Symfony/Component/Serializer/CHANGELOG.md b/src/Symfony/Component/Serializer/CHANGELOG.md index 19eaa6a9cb8a4..12269eda7ff4f 100644 --- a/src/Symfony/Component/Serializer/CHANGELOG.md +++ b/src/Symfony/Component/Serializer/CHANGELOG.md @@ -5,6 +5,7 @@ CHANGELOG --- * Add `XmlEncoder::SAVE_OPTIONS` context option + * Add `BackedEnumNormalizer::ALLOW_INVALID_VALUES` context option * Deprecate `MissingConstructorArgumentsException` in favor of `MissingConstructorArgumentException` 6.2 diff --git a/src/Symfony/Component/Serializer/Context/Normalizer/BackedEnumNormalizerContextBuilder.php b/src/Symfony/Component/Serializer/Context/Normalizer/BackedEnumNormalizerContextBuilder.php new file mode 100644 index 0000000000000..ca1a4f50a4a5f --- /dev/null +++ b/src/Symfony/Component/Serializer/Context/Normalizer/BackedEnumNormalizerContextBuilder.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Context\Normalizer; + +use Symfony\Component\Serializer\Context\ContextBuilderInterface; +use Symfony\Component\Serializer\Context\ContextBuilderTrait; +use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; + +/** + * A helper providing autocompletion for available BackedEnumNormalizer options. + * + * @author Nicolas PHILIPPE + */ +final class BackedEnumNormalizerContextBuilder implements ContextBuilderInterface +{ + use ContextBuilderTrait; + + /** + * Configures if invalid values are allowed in denormalization. + * They will be denormalized into `null` values. + */ + public function withAllowInvalidValues(bool $allowInvalidValues): static + { + return $this->with(BackedEnumNormalizer::ALLOW_INVALID_VALUES, $allowInvalidValues); + } +} diff --git a/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php index 26943377b654a..2281849ad20a4 100644 --- a/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php +++ b/src/Symfony/Component/Serializer/Normalizer/BackedEnumNormalizer.php @@ -22,6 +22,11 @@ */ final class BackedEnumNormalizer implements NormalizerInterface, DenormalizerInterface, CacheableSupportsMethodInterface { + /** + * If true, will denormalize any invalid value into null. + */ + public const ALLOW_INVALID_VALUES = 'allow_invalid_values'; + public function normalize(mixed $object, string $format = null, array $context = []): int|string { if (!$object instanceof \BackedEnum) { @@ -45,6 +50,18 @@ public function denormalize(mixed $data, string $type, string $format = null, ar throw new InvalidArgumentException('The data must belong to a backed enumeration.'); } + if ($context[self::ALLOW_INVALID_VALUES] ?? false) { + if (null === $data || (!\is_int($data) && !\is_string($data))) { + return null; + } + + try { + return $type::tryFrom($data); + } catch (\TypeError) { + return null; + } + } + if (!\is_int($data) && !\is_string($data)) { throw NotNormalizableValueException::createForUnexpectedDataType('The data is neither an integer nor a string, you should pass an integer or a string that can be parsed as an enumeration case of type '.$type.'.', $data, [Type::BUILTIN_TYPE_INT, Type::BUILTIN_TYPE_STRING], $context['deserialization_path'] ?? null, true); } diff --git a/src/Symfony/Component/Serializer/Tests/Context/Normalizer/BackedEnumNormalizerContextBuilderTest.php b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/BackedEnumNormalizerContextBuilderTest.php new file mode 100644 index 0000000000000..94d98776f0ab8 --- /dev/null +++ b/src/Symfony/Component/Serializer/Tests/Context/Normalizer/BackedEnumNormalizerContextBuilderTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Serializer\Tests\Context\Normalizer; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Serializer\Context\Normalizer\BackedEnumNormalizerContextBuilder; +use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer; + +class BackedEnumNormalizerContextBuilderTest extends TestCase +{ + private BackedEnumNormalizerContextBuilder $contextBuilder; + + protected function setUp(): void + { + $this->contextBuilder = new BackedEnumNormalizerContextBuilder(); + } + + public function testWithers() + { + $context = $this->contextBuilder->withAllowInvalidValues(true)->toArray(); + self::assertSame([BackedEnumNormalizer::ALLOW_INVALID_VALUES => true], $context); + + $context = $this->contextBuilder->withAllowInvalidValues(false)->toArray(); + self::assertSame([BackedEnumNormalizer::ALLOW_INVALID_VALUES => false], $context); + } +} diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/BackedEnumNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/BackedEnumNormalizerTest.php index b0063da5fe4e7..aa0cefe0b431c 100644 --- a/src/Symfony/Component/Serializer/Tests/Normalizer/BackedEnumNormalizerTest.php +++ b/src/Symfony/Component/Serializer/Tests/Normalizer/BackedEnumNormalizerTest.php @@ -114,4 +114,19 @@ public function testSupportsNormalizationShouldFailOnAnyPHPVersionForNonEnumObje { $this->assertFalse($this->normalizer->supportsNormalization(new \stdClass())); } + + public function testItUsesTryFromIfContextIsPassed() + { + $this->assertNull($this->normalizer->denormalize(1, IntegerBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + $this->assertNull($this->normalizer->denormalize('', IntegerBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + $this->assertNull($this->normalizer->denormalize(null, IntegerBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + + $this->assertSame(IntegerBackedEnumDummy::SUCCESS, $this->normalizer->denormalize(200, IntegerBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + + $this->assertNull($this->normalizer->denormalize(1, StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + $this->assertNull($this->normalizer->denormalize('foo', StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + $this->assertNull($this->normalizer->denormalize(null, StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + + $this->assertSame(StringBackedEnumDummy::GET, $this->normalizer->denormalize('GET', StringBackedEnumDummy::class, null, [BackedEnumNormalizer::ALLOW_INVALID_VALUES => true])); + } }