Skip to content

Commit 6ab0182

Browse files
KorvinSzantofabpot
authored andcommitted
[Serializer] Handle invalid mapping type property type
1 parent ff10bca commit 6ab0182

File tree

4 files changed

+73
-1
lines changed

4 files changed

+73
-1
lines changed

src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,10 @@ private function getMappedClass(array $data, string $class, array $context): str
11591159
throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('Type property "%s" not found for the abstract object "%s".', $mapping->getTypeProperty(), $class), null, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false);
11601160
}
11611161

1162+
if (!\is_string($type) && (!\is_object($type) || !method_exists($type, '__toString'))) {
1163+
throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type property "%s" for the abstract object "%s" must be a string or a stringable object.', $mapping->getTypeProperty(), $class), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), false);
1164+
}
1165+
11621166
if (null === $mappedClass = $mapping->getClassForType($type)) {
11631167
throw NotNormalizableValueException::createForUnexpectedDataType(\sprintf('The type "%s" is not a valid value.', $type), $type, ['string'], isset($context['deserialization_path']) ? $context['deserialization_path'].'.'.$mapping->getTypeProperty() : $mapping->getTypeProperty(), true);
11641168
}

src/Symfony/Component/Serializer/Tests/Fixtures/Attributes/AbstractDummyFirstChild.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
class AbstractDummyFirstChild extends AbstractDummy
1717
{
1818
public $bar;
19+
public $baz;
1920

2021
/** @var DummyFirstChildQuux|null */
2122
public $quux;

src/Symfony/Component/Serializer/Tests/Normalizer/AbstractObjectNormalizerTest.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,73 @@ private function getDenormalizerForStringCollection()
525525
return $denormalizer;
526526
}
527527

528+
/**
529+
* @dataProvider provideInvalidDiscriminatorTypes
530+
*/
531+
public function testDenormalizeWithDiscriminatorMapHandlesInvalidTypeValue(mixed $typeValue, bool $shouldFail)
532+
{
533+
if ($shouldFail) {
534+
$this->expectException(NotNormalizableValueException::class);
535+
$this->expectExceptionMessage(
536+
'The type property "type" for the abstract object "Symfony\Component\Serializer\Tests\Fixtures\Attributes\AbstractDummy" must be a string or a stringable object.'
537+
);
538+
}
539+
540+
$factory = new ClassMetadataFactory(new AttributeLoader());
541+
542+
$loaderMock = new class implements ClassMetadataFactoryInterface {
543+
public function getMetadataFor($value): ClassMetadataInterface
544+
{
545+
if (AbstractDummy::class === $value) {
546+
return new ClassMetadata(
547+
AbstractDummy::class,
548+
new ClassDiscriminatorMapping('type', [
549+
'first' => AbstractDummyFirstChild::class,
550+
'second' => AbstractDummySecondChild::class,
551+
])
552+
);
553+
}
554+
555+
throw new InvalidArgumentException();
556+
}
557+
558+
public function hasMetadataFor($value): bool
559+
{
560+
return AbstractDummy::class === $value;
561+
}
562+
};
563+
564+
$discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock);
565+
$normalizer = new AbstractObjectNormalizerDummy($factory, null, new ReflectionExtractor(), $discriminatorResolver);
566+
$serializer = new Serializer([$normalizer]);
567+
$normalizer->setSerializer($serializer);
568+
$normalizedData = $normalizer->denormalize(['foo' => 'foo', 'baz' => 'baz', 'quux' => ['value' => 'quux'], 'type' => $typeValue], AbstractDummy::class);
569+
570+
$this->assertInstanceOf(DummyFirstChildQuux::class, $normalizedData->quux);
571+
}
572+
573+
/**
574+
* @return iterable<array{0: mixed, 1: bool}>
575+
*/
576+
public static function provideInvalidDiscriminatorTypes(): array
577+
{
578+
$toStringObject = new class {
579+
public function __toString()
580+
{
581+
return 'first';
582+
}
583+
};
584+
585+
return [
586+
[[], true],
587+
[new \stdClass(), true],
588+
[123, true],
589+
[false, true],
590+
['first', false],
591+
[$toStringObject, false],
592+
];
593+
}
594+
528595
public function testDenormalizeWithDiscriminatorMapUsesCorrectClassname()
529596
{
530597
$factory = new ClassMetadataFactory(new AttributeLoader());

src/Symfony/Component/Serializer/Tests/SerializerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,7 @@ public function hasMetadataFor($value): bool
452452
$discriminatorResolver = new ClassDiscriminatorFromClassMetadata($loaderMock);
453453
$serializer = new Serializer([new ObjectNormalizer(null, null, null, new PhpDocExtractor(), $discriminatorResolver)], ['json' => new JsonEncoder()]);
454454

455-
$jsonData = '{"type":"first","quux":{"value":"quux"},"bar":"bar-value","foo":"foo-value"}';
455+
$jsonData = '{"type":"first","quux":{"value":"quux"},"bar":"bar-value","baz":null,"foo":"foo-value"}';
456456

457457
$deserialized = $serializer->deserialize($jsonData, AbstractDummy::class, 'json');
458458
$this->assertEquals($example, $deserialized);

0 commit comments

Comments
 (0)