Closed
Description
Symfony version(s) affected
5.4.12 and newer
6.x
Description
Hello,
It becomes impossible to deserialize a union type that contains a BackedEnum
, because InvalidArgumentException
is not caught.
It seems that a bug is introduced with the commit symfony/serializer@3fc9afe.
How to reproduce
composer.json
{
"require": {
"symfony/serializer": "^5.4",
"symfony/property-access": "^5.4"
}
}
index.php
<?php
require_once __DIR__.'/vendor/autoload.php';
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
Enum SubEnumA : string {
case Toto = 'toto';
case Toto2 = 'toto2';
}
Enum SubEnumB : string {
case Tata = 'tata';
case Tata2 = 'tata2';
}
class A {
public SubEnumA|SubEnumB $sub;
public function __construct(SubEnumA|SubEnumB $sub)
{
$this->sub = $sub;
}
}
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader());
$encoders = [new JsonEncoder()];
$reflectionExtractor = new ReflectionExtractor();
$propertyInfoExtractor = new PropertyInfoExtractor(
[$reflectionExtractor],
[$reflectionExtractor],
[],
[$reflectionExtractor],
[$reflectionExtractor]
);
$normalizers = [
new BackedEnumNormalizer(),
new ObjectNormalizer($classMetadataFactory, null, null, $propertyInfoExtractor),
];
$serializer = new Serializer($normalizers, $encoders);
$a = new A(SubEnumB::Tata);
$data = $serializer->serialize($a, 'json');
$a = $serializer->deserialize($data, A::class, 'json', [
AbstractNormalizer::ALLOW_EXTRA_ATTRIBUTES => false,
]);
var_dump($a);
Deserialize A does not work
Expected :
object(A)#35 (1) {
["sub"]=>
enum(SubEnumB::Tata)
}
Result :
PHP Fatal error: Uncaught Symfony\Component\Serializer\Exception\InvalidArgumentException: The data must belong to a backed enumeration of type SubEnumA in /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/BackedEnumNormalizer.php:67
Stack trace:
#0 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Serializer.php(238): Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer->denormalize()
#1 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(567): Symfony\Component\Serializer\Serializer->denormalize()
#2 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(635): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->validateAndDenormalize()
#3 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractNormalizer.php(383): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalizeParameter()
#4 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(281): Symfony\Component\Serializer\Normalizer\AbstractNormalizer->instantiateObject()
#5 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/AbstractObjectNormalizer.php(363): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->instantiateObject()
#6 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Serializer.php(238): Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer->denormalize()
#7 /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Serializer.php(151): Symfony\Component\Serializer\Serializer->denormalize()
#8 /home/thibault/workspace/mpp-api/issue_XXX.php(54): Symfony\Component\Serializer\Serializer->deserialize()
#9 {main}
thrown in /home/thibault/workspace/mpp-api/vendor/symfony/serializer/Normalizer/BackedEnumNormalizer.php on line 67
Possible Solution
In method denormalize
on Symfony\Component\Serializer\NormalizerBackedEnumNormalizer
change InvalidArgumentException
by NotNormalizableValueException
/**
* {@inheritdoc}
*
* @throws NotNormalizableValueException
*/
public function denormalize($data, string $type, string $format = null, array $context = [])
{
if (!is_subclass_of($type, \BackedEnum::class)) {
throw new InvalidArgumentException('The data must belong to a backed enumeration.');
}
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);
}
try {
return $type::from($data);
} catch (\ValueError $e) {
throw new InvalidArgumentException('The data must belong to a backed enumeration of type '.$type);
}
}
From:
try {
return $type::from($data);
} catch (\ValueError $e) {
throw new InvalidArgumentException('The data must belong to a backed enumeration of type '.$type);
}
To:
try {
return $type::from($data);
} catch (\ValueError $e) {
throw new NotNormalizableValueException('The data must belong to a backed enumeration of type '.$type);
}
Additional Context
No response