diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 80b31b0a3fd..48dd5f46b75 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -315,7 +315,13 @@ protected function denormalizeRelation(string $attributeName, PropertyMetadata $ // repeat the code so that IRIs keep working with the json format if (true === $this->allowPlainIdentifiers && $this->itemDataProvider) { try { - return $this->itemDataProvider->getItem($className, $value, null, $context + ['fetch_data' => true]); + $item = $this->itemDataProvider->getItem($className, $value, null, $context + ['fetch_data' => true]); + + if (null === $item) { + throw new ItemNotFoundException(sprintf('Item with id "%s" not found for class "%s".', $value, $className)); + } + + return $item; } catch (ItemNotFoundException $e) { throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); } catch (InvalidArgumentException $e) { diff --git a/tests/Serializer/AbstractItemNormalizerTest.php b/tests/Serializer/AbstractItemNormalizerTest.php index 2207508687f..8a2fc7bea41 100644 --- a/tests/Serializer/AbstractItemNormalizerTest.php +++ b/tests/Serializer/AbstractItemNormalizerTest.php @@ -17,6 +17,7 @@ use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; +use ApiPlatform\Core\Exception\ItemNotFoundException; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Metadata\Property\PropertyMetadata; @@ -287,6 +288,73 @@ public function testDenormalize() ], Dummy::class); } + /** + * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException + * @expectedExceptionMessage NotFound exception + */ + public function testDenormalizeNotFound() + { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn( + new PropertyNameCollection(['name', 'relatedDummy', 'relatedDummies']) + )->shouldBeCalled(); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn( + new PropertyMetadata(new Type(Type::BUILTIN_TYPE_STRING), '', false, true) + )->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn( + new PropertyMetadata( + new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class), + '', + false, + true, + false, + false + ) + )->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummies', [])->willReturn( + new PropertyMetadata( + new Type(Type::BUILTIN_TYPE_OBJECT, + false, + ArrayCollection::class, + true, + new Type(Type::BUILTIN_TYPE_INT), + new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class) + ), + '', + false, + true, + false, + false + ) + )->shouldBeCalled(); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $iriConverterProphecy->getItemFromIri('/dummies/1', Argument::type('array'))->willThrow(new ItemNotFoundException('NotFound exception'))->shouldBeCalled(); + + $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->willImplement(NormalizerInterface::class); + + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ + $propertyNameCollectionFactoryProphecy->reveal(), + $propertyMetadataFactoryProphecy->reveal(), + $iriConverterProphecy->reveal(), + $resourceClassResolverProphecy->reveal(), + $propertyAccessorProphecy->reveal(), + ]); + $normalizer->setSerializer($serializerProphecy->reveal()); + + $normalizer->denormalize([ + 'name' => 'foo', + 'relatedDummy' => '/dummies/1', + ], Dummy::class); + } + public function testDenormalizeWritableLinks() { $relatedDummy1 = new RelatedDummy(); @@ -676,7 +744,58 @@ public function testDenormalizeRelationWithPlainId() $serializerProphecy->willImplement(DenormalizerInterface::class); $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy->getItem(RelatedDummy::class, 1, null, Argument::type('array'))->shouldBeCalled(); + $itemDataProviderProphecy->getItem(RelatedDummy::class, 1, null, Argument::type('array'))->willReturn(new RelatedDummy())->shouldBeCalled(); + + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ + $propertyNameCollectionFactoryProphecy->reveal(), + $propertyMetadataFactoryProphecy->reveal(), + $iriConverterProphecy->reveal(), + $resourceClassResolverProphecy->reveal(), + $propertyAccessorProphecy->reveal(), + null, + null, + $itemDataProviderProphecy->reveal(), + true, + ]); + $normalizer->setSerializer($serializerProphecy->reveal()); + + $normalizer->denormalize(['relatedDummy' => 1], Dummy::class, 'jsonld'); + } + + /** + * @expectedException \ApiPlatform\Core\Exception\InvalidArgumentException + * @expectedExceptionMessage Item with id "1" not found for class "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy". + */ + public function testDenormalizeNotFoundRelationWithPlainId() + { + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn( + new PropertyNameCollection(['relatedDummy']) + )->shouldBeCalled(); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn( + new PropertyMetadata( + new Type(Type::BUILTIN_TYPE_OBJECT, false, RelatedDummy::class), + '', + false, + true, + false, + false + ) + )->shouldBeCalled(); + + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); + $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true)->shouldBeCalled(); + + $serializerProphecy = $this->prophesize(SerializerInterface::class); + $serializerProphecy->willImplement(DenormalizerInterface::class); + + $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); + $itemDataProviderProphecy->getItem(RelatedDummy::class, 1, null, Argument::type('array'))->willReturn(null)->shouldBeCalled(); $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(),