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

Skip to content

Commit 7c668b2

Browse files
Amoifrnicolas-grekas
authored andcommitted
[Serializer] Fix mixed-typed constructor parameters overriding getter-inferred type
1 parent 508be04 commit 7c668b2

2 files changed

Lines changed: 138 additions & 5 deletions

File tree

Normalizer/AbstractObjectNormalizer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
975975
// BC layer for PropertyTypeExtractorInterface::getTypes().
976976
// Can be removed as soon as PropertyTypeExtractorInterface::getTypes() is removed (8.0).
977977
if (\is_array($type)) {
978-
if (($parameterType = $parameter->getType()) instanceof \ReflectionNamedType) {
978+
if (($parameterType = $parameter->getType()) instanceof \ReflectionNamedType && 'mixed' !== $parameterType->getName()) {
979979
$matches = false;
980980

981981
if ($parameterType->isBuiltin()) {
@@ -1023,7 +1023,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
10231023
if (null !== $parameterType && $parameterTypeResolver ??= class_exists(ReflectionTypeResolver::class) ? new ReflectionTypeResolver() : false) {
10241024
$resolvedParameterType = $parameterTypeResolver->resolve($parameterType, ($parameterTypeContextFactory ??= new TypeContextFactory())->createFromClassName($class->name, $parameter->getDeclaringClass()?->name));
10251025
if ($resolvedParameterType->isSatisfiedBy(static fn (Type $t) => match (true) {
1026-
$t instanceof BuiltinType && TypeIdentifier::NULL !== $t->getTypeIdentifier() => !$type->isIdentifiedBy($t->getTypeIdentifier()),
1026+
$t instanceof BuiltinType && !\in_array($t->getTypeIdentifier(), [TypeIdentifier::NULL, TypeIdentifier::MIXED], true) => !$type->isIdentifiedBy($t->getTypeIdentifier()),
10271027
$t instanceof ObjectType => !$type->isIdentifiedBy($t->getClassName()),
10281028
default => false,
10291029
})) {
@@ -1033,7 +1033,7 @@ protected function denormalizeParameter(\ReflectionClass $class, \ReflectionPara
10331033
if ($parameterType->isBuiltin()) {
10341034
$typeIdentifier = TypeIdentifier::tryFrom($parameterType->getName());
10351035

1036-
if (null !== $typeIdentifier && !$type->isIdentifiedBy($typeIdentifier)) {
1036+
if (null !== $typeIdentifier && TypeIdentifier::MIXED !== $typeIdentifier && !$type->isIdentifiedBy($typeIdentifier)) {
10371037
$type = Type::builtin($typeIdentifier);
10381038
}
10391039
} elseif (!$type->isIdentifiedBy($parameterType->getName())) {

Tests/Normalizer/AbstractObjectNormalizerTest.php

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
namespace Symfony\Component\Serializer\Tests\Normalizer;
1313

1414
use PHPUnit\Framework\Attributes\DataProvider;
15-
use PHPUnit\Framework\Attributes\RequiresMethod;
15+
use PHPUnit\Framework\Attributes\Group;
16+
use PHPUnit\Framework\Attributes\IgnoreDeprecations;
1617
use PHPUnit\Framework\TestCase;
1718
use Symfony\Component\PropertyAccess\PropertyPath;
1819
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
@@ -979,7 +980,6 @@ public function testDenormalizeSelfConstructorPromotedParameter()
979980
$this->assertEquals(new DummyWithSelfConstructorPromotedParameter('A', new DummyWithSelfConstructorPromotedParameter('B')), $serializer->denormalize($normalized, DummyWithSelfConstructorPromotedParameter::class));
980981
}
981982

982-
#[RequiresMethod(ReflectionTypeResolver::class, 'resolve')]
983983
public function testDenormalizeUsesConstructorUnionTypeWhenExtractorIsLessPrecise()
984984
{
985985
$extractor = new class implements PropertyTypeExtractorInterface {
@@ -999,6 +999,118 @@ public function getTypes(string $class, string $property, array $context = []):
999999
$this->assertEquals(new DummyWithIntOrString(1), $serializer->denormalize(['value' => 1], DummyWithIntOrString::class));
10001000
}
10011001

1002+
public function testDenormalizeMixedConstructorParameterUsesExtractorType()
1003+
{
1004+
$extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]);
1005+
1006+
$entityDenormalizer = new class implements DenormalizerInterface {
1007+
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
1008+
{
1009+
return new DummyEntity((int) $data);
1010+
}
1011+
1012+
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
1013+
{
1014+
return DummyEntity::class === $type;
1015+
}
1016+
1017+
public function getSupportedTypes(?string $format): array
1018+
{
1019+
return [DummyEntity::class => true];
1020+
}
1021+
};
1022+
1023+
$serializer = new Serializer([
1024+
$entityDenormalizer,
1025+
new ObjectNormalizer(propertyTypeExtractor: $extractor),
1026+
]);
1027+
1028+
$result = $serializer->denormalize(['entity' => 42], DummyWithMixedConstructorParamAndEntityGetter::class);
1029+
1030+
$this->assertInstanceOf(DummyWithMixedConstructorParamAndEntityGetter::class, $result);
1031+
$this->assertInstanceOf(DummyEntity::class, $result->getEntity());
1032+
$this->assertSame(42, $result->getEntity()->id);
1033+
}
1034+
1035+
#[Group('legacy')]
1036+
#[IgnoreDeprecations]
1037+
public function testDenormalizeMixedConstructorParameterUsesExtractorTypeLegacy()
1038+
{
1039+
$extractor = new PropertyInfoExtractor([], [new ReflectionExtractor()]);
1040+
1041+
$entityDenormalizer = new class implements DenormalizerInterface {
1042+
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
1043+
{
1044+
return new DummyEntity((int) $data);
1045+
}
1046+
1047+
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
1048+
{
1049+
return DummyEntity::class === $type;
1050+
}
1051+
1052+
public function getSupportedTypes(?string $format): array
1053+
{
1054+
return [DummyEntity::class => true];
1055+
}
1056+
};
1057+
1058+
$serializer = new Serializer([
1059+
$entityDenormalizer,
1060+
new ObjectNormalizer(propertyTypeExtractor: $extractor),
1061+
]);
1062+
1063+
$result = $serializer->denormalize(['entity' => 42], DummyWithMixedConstructorParamAndEntityGetter::class);
1064+
1065+
$this->assertInstanceOf(DummyWithMixedConstructorParamAndEntityGetter::class, $result);
1066+
$this->assertInstanceOf(DummyEntity::class, $result->getEntity());
1067+
$this->assertSame(42, $result->getEntity()->id);
1068+
}
1069+
1070+
#[Group('legacy')]
1071+
#[IgnoreDeprecations]
1072+
public function testDenormalizeMixedConstructorParameterUsesExtractorTypeLegacyTypes()
1073+
{
1074+
$extractor = new class implements PropertyTypeExtractorInterface {
1075+
public function getTypes(string $class, string $property, array $context = []): ?array
1076+
{
1077+
if (DummyWithMixedConstructorParamAndEntityGetter::class === $class && 'entity' === $property) {
1078+
return [new LegacyType(LegacyType::BUILTIN_TYPE_OBJECT, true, DummyEntity::class)];
1079+
}
1080+
1081+
return null;
1082+
}
1083+
};
1084+
1085+
$entityDenormalizer = new class implements DenormalizerInterface {
1086+
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
1087+
{
1088+
return new DummyEntity((int) $data);
1089+
}
1090+
1091+
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
1092+
{
1093+
return DummyEntity::class === $type;
1094+
}
1095+
1096+
public function getSupportedTypes(?string $format): array
1097+
{
1098+
return [DummyEntity::class => true];
1099+
}
1100+
};
1101+
1102+
$serializer = new Serializer([
1103+
$entityDenormalizer,
1104+
new ObjectNormalizer(propertyTypeExtractor: $extractor),
1105+
]);
1106+
1107+
$result = $serializer->denormalize(['entity' => 42], DummyWithMixedConstructorParamAndEntityGetter::class);
1108+
1109+
$this->assertInstanceOf(DummyWithMixedConstructorParamAndEntityGetter::class, $result);
1110+
$this->assertInstanceOf(DummyEntity::class, $result->getEntity());
1111+
$this->assertSame(42, $result->getEntity()->id);
1112+
}
1113+
10021114
public function testDenormalizeWithNumberAsSerializedNameAndNoArrayReindex()
10031115
{
10041116
$normalizer = new AbstractObjectNormalizerWithMetadata();
@@ -2076,3 +2188,24 @@ class DummyGenericsValueWrapper
20762188
/** @var T[] */
20772189
public array $values;
20782190
}
2191+
2192+
class DummyEntity
2193+
{
2194+
public function __construct(
2195+
public int $id,
2196+
) {
2197+
}
2198+
}
2199+
2200+
class DummyWithMixedConstructorParamAndEntityGetter
2201+
{
2202+
public function __construct(
2203+
private mixed $entity = null,
2204+
) {
2205+
}
2206+
2207+
public function getEntity(): ?DummyEntity
2208+
{
2209+
return $this->entity;
2210+
}
2211+
}

0 commit comments

Comments
 (0)