Skip to content

Commit 99718d9

Browse files
authored
fix(serializer): resilient denormalizeRelation capability (#7474)
1 parent fafbe5c commit 99718d9

File tree

2 files changed

+44
-0
lines changed

2 files changed

+44
-0
lines changed

src/Serializer/AbstractItemNormalizer.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,10 @@ protected function denormalizeRelation(string $attributeName, ApiProperty $prope
581581
try {
582582
return $this->iriConverter->getResourceFromIri($value, $context + ['fetch_data' => true]);
583583
} catch (ItemNotFoundException $e) {
584+
if (false === ($context['denormalize_throw_on_relation_not_found'] ?? true)) {
585+
return null;
586+
}
587+
584588
if (!isset($context['not_normalizable_value_exceptions'])) {
585589
throw new UnexpectedValueException($e->getMessage(), $e->getCode(), $e);
586590
}

src/Serializer/Tests/AbstractItemNormalizerTest.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,46 @@ public function testDenormalizeWritableLinks(): void
792792
$propertyAccessorProphecy->setValue($actual, 'relatedDummiesWithUnionTypes', [0 => $relatedDummy3, 1. => $relatedDummy4])->shouldHaveBeenCalled();
793793
}
794794

795+
public function testDenormalizeRelationNotFoundReturnsNull(): void
796+
{
797+
$data = [
798+
'relatedDummy' => '/dummies/not_found',
799+
];
800+
801+
$propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class);
802+
$propertyNameCollectionFactoryProphecy->create(Dummy::class, Argument::type('array'))->willReturn(new PropertyNameCollection(['relatedDummy']));
803+
804+
$relatedDummyType = new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class);
805+
806+
$propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class);
807+
$propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', Argument::type('array'))->willReturn((new ApiProperty())->withBuiltinTypes([$relatedDummyType])->withReadable(false)->withWritable(true)->withReadableLink(false)->withWritableLink(false));
808+
809+
$iriConverterProphecy = $this->prophesize(IriConverterInterface::class);
810+
$iriConverterProphecy->getResourceFromIri('/dummies/not_found', Argument::type('array'))->willThrow(new ItemNotFoundException());
811+
812+
$propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class);
813+
814+
$resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class);
815+
$resourceClassResolverProphecy->getResourceClass(null, Dummy::class)->willReturn(Dummy::class);
816+
$resourceClassResolverProphecy->getResourceClass(null, RelatedDummy::class)->willReturn(RelatedDummy::class);
817+
$resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true);
818+
$resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true);
819+
820+
$serializerProphecy = $this->prophesize(SerializerInterface::class);
821+
$serializerProphecy->willImplement(DenormalizerInterface::class);
822+
823+
$normalizer = new class($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), null, null, [], null, null) extends AbstractItemNormalizer {};
824+
$normalizer->setSerializer($serializerProphecy->reveal());
825+
826+
$actual = $normalizer->denormalize($data, Dummy::class, null, [
827+
'denormalize_throw_on_relation_not_found' => false,
828+
'not_normalizable_value_exceptions' => [],
829+
]);
830+
831+
$this->assertInstanceOf(Dummy::class, $actual);
832+
$propertyAccessorProphecy->setValue($actual, 'relatedDummy', null)->shouldHaveBeenCalled();
833+
}
834+
795835
public function testBadRelationType(): void
796836
{
797837
$this->expectException(NotNormalizableValueException::class);

0 commit comments

Comments
 (0)