diff --git a/features/main/custom_normalized.feature b/features/main/custom_normalized.feature index f9f636bdb4c..b8d1209f060 100644 --- a/features/main/custom_normalized.feature +++ b/features/main/custom_normalized.feature @@ -50,7 +50,7 @@ Feature: Using custom normalized entity """ Scenario: Create a resource with a custom normalized dummy and an id - When I add "Content-Type" header equal to "application/json" + When I add "Content-Type" header equal to "application/ld+json" When I add "Accept" header equal to "application/json" And I send a "PUT" request to "/related_normalized_dummies/1" with body: """ @@ -58,7 +58,7 @@ Feature: Using custom normalized entity "name": "My Dummy", "customNormalizedDummy":[{ "@context": "/contexts/CustomNormalizedDummy", - "@id": "/custom_normalized_dummies/1", + "@id": "app_dev.php/custom_normalized_dummies/1", "@type": "CustomNormalizedDummy", "id": 1, "name": "My Dummy" diff --git a/features/main/writable_identifier.feature b/features/main/writable_identifier.feature new file mode 100644 index 00000000000..1e6cf9d8f09 --- /dev/null +++ b/features/main/writable_identifier.feature @@ -0,0 +1,53 @@ +Feature: Writable Identifier + In order to use a hypermedia API + As a client software developer + I need to be able to create resources with a writable identifier + + @createSchema + Scenario: Create a resource with a custom identifier + When I add "Content-Type" header equal to "application/ld+json" + And I send a "POST" request to "/writable_ids" with body: + """ + { + "id": "7995560a-4e09-4bba-8950-963528d004f0", + "name": "Foo" + } + """ + Then the response status code should be 201 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/WritableId", + "@id": "/writable_ids/7995560a-4e09-4bba-8950-963528d004f0", + "@type": "WritableId", + "id": "7995560a-4e09-4bba-8950-963528d004f0", + "name": "Foo" + } + """ + + @dropSchema + Scenario: Update a resource with a custom identifier + When I add "Content-Type" header equal to "application/ld+json" + And I send a "PUT" request to "/writable_ids/7995560a-4e09-4bba-8950-963528d004f0" with body: + """ + { + "@id": "/writable_ids/7995560a-4e09-4bba-8950-963528d004f1", + "id": "7995560a-4e09-4bba-8950-963528d004f1", + "name": "Foo" + } + """ + Then the response status code should be 200 + And the response should be in JSON + And the header "Content-Type" should be equal to "application/ld+json; charset=utf-8" + And the JSON should be equal to: + """ + { + "@context": "/contexts/WritableId", + "@id": "/writable_ids/7995560a-4e09-4bba-8950-963528d004f1", + "@type": "WritableId", + "id": "7995560a-4e09-4bba-8950-963528d004f1", + "name": "Foo" + } + """ diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 245bdec48f5..ba31a76fd28 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -89,6 +89,7 @@ %api_platform.allow_plain_identifiers% + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml b/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml index bdc491d1b38..b4906510708 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/graphql.xml @@ -76,6 +76,7 @@ %api_platform.allow_plain_identifiers% + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml b/src/Bridge/Symfony/Bundle/Resources/config/hal.xml index 9e6a3f5d358..ec68f26b7e8 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/hal.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/hal.xml @@ -34,6 +34,9 @@ + + %api_platform.allow_plain_identifiers% + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml b/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml index cc1fd13cd1b..ec857cffc96 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/jsonapi.xml @@ -39,6 +39,7 @@ + diff --git a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml b/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml index 999ce4ff92b..3b453208f7b 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/jsonld.xml @@ -25,6 +25,7 @@ + diff --git a/src/JsonApi/Serializer/ItemNormalizer.php b/src/JsonApi/Serializer/ItemNormalizer.php index 0d0f754f0dd..6d018273eed 100644 --- a/src/JsonApi/Serializer/ItemNormalizer.php +++ b/src/JsonApi/Serializer/ItemNormalizer.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\JsonApi\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\Api\ResourceClassResolverInterface; @@ -40,9 +41,9 @@ final class ItemNormalizer extends AbstractItemNormalizer private $componentsCache = []; private $resourceMetadataFactory; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ResourceMetadataFactoryInterface $resourceMetadataFactory) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ResourceMetadataFactoryInterface $resourceMetadataFactory, IdentifiersExtractorInterface $identifiersExtractor = null) { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter); + parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, null, null, false, $identifiersExtractor); $this->resourceMetadataFactory = $resourceMetadataFactory; } @@ -108,18 +109,6 @@ public function supportsDenormalization($data, $type, $format = null) */ public function denormalize($data, $class, $format = null, array $context = []) { - // Avoid issues with proxies if we populated the object - if (isset($data['data']['id']) && !isset($context[self::OBJECT_TO_POPULATE])) { - if (isset($context['api_allow_update']) && true !== $context['api_allow_update']) { - throw new InvalidArgumentException('Update is not allowed for this operation.'); - } - - $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri( - $data['data']['id'], - $context + ['fetch_data' => false] - ); - } - // Merge attributes and relations previous to apply parents denormalizing $dataToDenormalize = array_merge( $data['data']['attributes'] ?? [], diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php index 5756b141e45..4d3b87d9180 100644 --- a/src/JsonLd/Serializer/ItemNormalizer.php +++ b/src/JsonLd/Serializer/ItemNormalizer.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\JsonLd\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; @@ -41,9 +42,9 @@ final class ItemNormalizer extends AbstractItemNormalizer private $resourceMetadataFactory; private $contextBuilder; - public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ContextBuilderInterface $contextBuilder, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null) + public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, ContextBuilderInterface $contextBuilder, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, IdentifiersExtractorInterface $identifiersExtractor = null) { - parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory); + parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter, $classMetadataFactory, null, false, $identifiersExtractor); $this->resourceMetadataFactory = $resourceMetadataFactory; $this->contextBuilder = $contextBuilder; @@ -91,20 +92,36 @@ public function supportsDenormalization($data, $type, $format = null) /** * {@inheritdoc} - * - * @throws InvalidArgumentException */ - public function denormalize($data, $class, $format = null, array $context = []) + protected function setObjectToPopulate($data, string $class, array &$context) { - // Avoid issues with proxies if we populated the object - if (isset($data['@id']) && !isset($context[self::OBJECT_TO_POPULATE])) { - if (isset($context['api_allow_update']) && true !== $context['api_allow_update']) { - throw new InvalidArgumentException('Update is not allowed for this operation.'); - } + try { + parent::setObjectToPopulate($data, $class, $context); - $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri($data['@id'], $context + ['fetch_data' => true]); + return; + } catch (InvalidArgumentException $e) { } - return parent::denormalize($data, $class, $format, $context); + // https://github.com/api-platform/core/issues/857 + $identifiers = $this->identifiersExtractor->getIdentifiersFromResourceClass($class); + $identifiersData = \array_intersect_key($data, array_flip($identifiers)); + if (0 === \count($identifiersData)) { + throw $e; + } + + // TODO: use $this->iriConverter->getIriFromPlainIdentifier() once https://github.com/api-platform/core/pull/1837 is merged. + $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri( + sprintf( + '%s/%s', + $this->iriConverter->getIriFromResourceClass($context['resource_class']), + implode(';', $identifiersData) + ), + $context + ['fetch_data' => true] + ); + } + + protected function getIdentifiersForDenormalization(string $class): array + { + return ['@id']; } } diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 4d3cf52d355..a10460bd7c0 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\OperationType; use ApiPlatform\Core\Api\ResourceClassResolverInterface; @@ -50,8 +51,9 @@ abstract class AbstractItemNormalizer extends AbstractObjectNormalizer protected $localCache = []; protected $itemDataProvider; protected $allowPlainIdentifiers; + protected $identifiersExtractor; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, ItemDataProviderInterface $itemDataProvider = null, bool $allowPlainIdentifiers = false) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ClassMetadataFactoryInterface $classMetadataFactory = null, ItemDataProviderInterface $itemDataProvider = null, bool $allowPlainIdentifiers = false, IdentifiersExtractorInterface $identifiersExtractor = null) { parent::__construct($classMetadataFactory, $nameConverter); @@ -66,6 +68,12 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName $this->setCircularReferenceHandler(function ($object) { return $this->iriConverter->getIriFromItem($object); }); + + if (null === $identifiersExtractor) { + @trigger_error(sprintf('Not passing an instance of IdentifiersExtractorInterface in "%s" is deprecated and will be mandatory in version 3.0. Not passing it is deprecated since 2.2.', __METHOD__), E_USER_DEPRECATED); + } + + $this->identifiersExtractor = $identifiersExtractor; } /** @@ -115,6 +123,8 @@ public function denormalize($data, $class, $format = null, array $context = []) $context['resource_class'] = $class; } + $this->setObjectToPopulate($data, $class, $context); + return parent::denormalize($data, $class, $format, $context); } @@ -488,4 +498,51 @@ protected function normalizeRelation(PropertyMetadata $propertyMetadata, $relate return $iri; } + + /** + * Sets the object to populate if allowed to and not set already. If you pass identifiers, it will search for them, + * otherwise it falls back to the provided IdentifiersExtractor. + * + * @param mixed $data + * @param string $class + * @param array $context + */ + protected function setObjectToPopulate($data, string $class, array &$context) + { + if (isset($context[self::OBJECT_TO_POPULATE]) || !\is_array($data)) { + return; + } + + $updateAllowed = $context['api_allow_update'] ?? false; + $identifiers = $this->getIdentifiersForDenormalization($class); + $identifiersData = \array_intersect_key($data, array_flip($identifiers)); + + if (0 === \count($identifiersData)) { + return; + } + + if (!$updateAllowed) { + throw new InvalidArgumentException('Update is not allowed for this operation.'); + } + + // TODO: use $this->iriConverter->getIriFromPlainIdentifier() once https://github.com/api-platform/core/pull/1837 is merged. + $iri = implode(';', $identifiersData); + + $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri($iri, $context + ['fetch_data' => true]); + } + + /** + * Returns the identifiers used when denormalizing an object. + * Usually this is just what the IdentifiersExtractor provides but you might want to override this method in your + * normalizer depending on the format used. Note: They also have to be writeable to serve as proper identifier. + */ + protected function getIdentifiersForDenormalization(string $class): array + { + // Backwards compatibility + if (null === $this->identifiersExtractor) { + return ['id']; + } + + return $this->identifiersExtractor->getIdentifiersFromResourceClass($class); + } } diff --git a/src/Serializer/ItemNormalizer.php b/src/Serializer/ItemNormalizer.php index 36bb1799f1d..52289570d5a 100644 --- a/src/Serializer/ItemNormalizer.php +++ b/src/Serializer/ItemNormalizer.php @@ -13,8 +13,6 @@ namespace ApiPlatform\Core\Serializer; -use ApiPlatform\Core\Exception\InvalidArgumentException; - /** * Generic item normalizer. * @@ -24,43 +22,4 @@ */ class ItemNormalizer extends AbstractItemNormalizer { - /** - * {@inheritdoc} - * - * @throws InvalidArgumentException - */ - public function denormalize($data, $class, $format = null, array $context = []) - { - // Avoid issues with proxies if we populated the object - if (isset($data['id']) && !isset($context[self::OBJECT_TO_POPULATE])) { - if (isset($context['api_allow_update']) && true !== $context['api_allow_update']) { - throw new InvalidArgumentException('Update is not allowed for this operation.'); - } - - $this->updateObjectToPopulate($data, $context); - } - - return parent::denormalize($data, $class, $format, $context); - } - - private function updateObjectToPopulate(array $data, array &$context) - { - try { - $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri((string) $data['id'], $context + ['fetch_data' => true]); - } catch (InvalidArgumentException $e) { - $identifier = null; - foreach ($this->propertyNameCollectionFactory->create($context['resource_class'], $context) as $propertyName) { - if (true === $this->propertyMetadataFactory->create($context['resource_class'], $propertyName)->isIdentifier()) { - $identifier = $propertyName; - break; - } - } - - if (null === $identifier) { - throw $e; - } - - $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri(sprintf('%s/%s', $this->iriConverter->getIriFromResourceClass($context['resource_class']), $data[$identifier]), $context + ['fetch_data' => true]); - } - } } diff --git a/tests/GraphQl/Serializer/ItemNormalizerTest.php b/tests/GraphQl/Serializer/ItemNormalizerTest.php index 60a1b4ec566..18b2244fdc4 100644 --- a/tests/GraphQl/Serializer/ItemNormalizerTest.php +++ b/tests/GraphQl/Serializer/ItemNormalizerTest.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\GraphQl\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\GraphQl\Serializer\ItemNormalizer; @@ -46,11 +47,19 @@ public function testSupportNormalization() $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true)->shouldBeCalled(); $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal() + $resourceClassResolverProphecy->reveal(), + null, + null, + null, + null, + false, + $identifiersExtractor->reveal() ); $this->assertTrue($normalizer->supportsNormalization($dummy, ItemNormalizer::FORMAT)); @@ -85,11 +94,19 @@ public function testNormalize() $serializerProphecy->willImplement(NormalizerInterface::class); $serializerProphecy->normalize('hello', ItemNormalizer::FORMAT, Argument::type('array'))->willReturn('hello')->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal() + $resourceClassResolverProphecy->reveal(), + null, + null, + null, + null, + false, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -115,11 +132,20 @@ public function testDenormalize() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal() + $resourceClassResolverProphecy->reveal(), + null, + null, + null, + null, + false, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); diff --git a/tests/Hal/Serializer/ItemNormalizerTest.php b/tests/Hal/Serializer/ItemNormalizerTest.php index 572f349c2ba..e8c8285a8f2 100644 --- a/tests/Hal/Serializer/ItemNormalizerTest.php +++ b/tests/Hal/Serializer/ItemNormalizerTest.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\Hal\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Exception\RuntimeException; @@ -50,6 +51,7 @@ public function testDoesNotSupportDenormalization() $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $nameConverter = $this->prophesize(NameConverterInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), @@ -57,7 +59,11 @@ public function testDoesNotSupportDenormalization() $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), null, - $nameConverter->reveal() + $nameConverter->reveal(), + null, + null, + false, + $identifiersExtractor->reveal() ); $this->assertFalse($normalizer->supportsDenormalization('foo', ItemNormalizer::FORMAT)); @@ -80,13 +86,19 @@ public function testSupportsNormalization() $nameConverter = $this->prophesize(NameConverterInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), null, - $nameConverter->reveal() + $nameConverter->reveal(), + null, + null, + false, + $identifiersExtractor->reveal() ); $this->assertTrue($normalizer->supportsNormalization($dummy, 'jsonhal')); @@ -130,13 +142,19 @@ public function testNormalize() $nameConverter->normalize('name')->shouldBeCalled()->willReturn('name'); $nameConverter->normalize('relatedDummy')->shouldBeCalled()->willReturn('related_dummy'); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), null, - $nameConverter->reveal() + $nameConverter->reveal(), + null, + null, + false, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -190,13 +208,19 @@ public function testNormalizeWithoutCache() $nameConverter->normalize('name')->shouldBeCalled()->willReturn('name'); $nameConverter->normalize('relatedDummy')->shouldBeCalled()->willReturn('related_dummy'); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), null, - $nameConverter->reveal() + $nameConverter->reveal(), + null, + null, + false, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -270,7 +294,10 @@ public function testMaxDepth() $resourceClassResolverProphecy->reveal(), null, null, - $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())) + $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())), + null, + false, + $this->prophesize(IdentifiersExtractorInterface::class)->reveal() ); $serializer = new Serializer([$normalizer]); $normalizer->setSerializer($serializer); diff --git a/tests/Hydra/Serializer/ItemNormalizerTest.php b/tests/Hydra/Serializer/ItemNormalizerTest.php index f91b82bddd7..fb0e5403db5 100644 --- a/tests/Hydra/Serializer/ItemNormalizerTest.php +++ b/tests/Hydra/Serializer/ItemNormalizerTest.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\Hydra\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\JsonLd\ContextBuilderInterface; @@ -44,8 +45,20 @@ public function testDontSupportDenormalization() $contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class); $resourceClassResolverProphecy->getResourceClass(['dummy'], 'Dummy')->willReturn(Dummy::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class, [])->willReturn(new PropertyNameCollection(['name' => 'name'])); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); - $normalizer = new ItemNormalizer($resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $contextBuilderProphecy->reveal()); + $normalizer = new ItemNormalizer( + $resourceMetadataFactoryProphecy->reveal(), + $propertyNameCollectionFactoryProphecy->reveal(), + $propertyMetadataFactoryProphecy->reveal(), + $iriConverterProphecy->reveal(), + $resourceClassResolverProphecy->reveal(), + $contextBuilderProphecy->reveal(), + null, + null, + null, + $identifiersExtractor->reveal() + ); $this->assertFalse($normalizer->supportsDenormalization('foo', ItemNormalizer::FORMAT)); } @@ -66,13 +79,19 @@ public function testSupportNormalization() $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true)->shouldBeCalled(); $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $contextBuilderProphecy->reveal() + $contextBuilderProphecy->reveal(), + null, + null, + null, + $identifiersExtractor->reveal() ); $this->assertTrue($normalizer->supportsNormalization($dummy, 'jsonld')); @@ -108,13 +127,19 @@ public function testNormalize() $contextBuilderProphecy = $this->prophesize(ContextBuilderInterface::class); $contextBuilderProphecy->getResourceContextUri(Dummy::class)->willReturn('/contexts/Dummy'); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $resourceMetadataFactoryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), - $contextBuilderProphecy->reveal() + $contextBuilderProphecy->reveal(), + null, + null, + null, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); diff --git a/tests/JsonApi/Serializer/ItemNormalizerTest.php b/tests/JsonApi/Serializer/ItemNormalizerTest.php index 38a2b7cab48..1ee687a8105 100644 --- a/tests/JsonApi/Serializer/ItemNormalizerTest.php +++ b/tests/JsonApi/Serializer/ItemNormalizerTest.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\JsonApi\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; @@ -47,6 +48,8 @@ public function testSupportDenormalization() $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true)->shouldBeCalled(); $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), @@ -54,7 +57,8 @@ public function testSupportDenormalization() $resourceClassResolverProphecy->reveal(), null, null, - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); $this->assertTrue($normalizer->supportsDenormalization(null, Dummy::class, ItemNormalizer::FORMAT)); @@ -70,6 +74,8 @@ public function testSupportNormalization() $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true)->shouldBeCalled(); $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), @@ -77,7 +83,8 @@ public function testSupportNormalization() $resourceClassResolverProphecy->reveal(), null, null, - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); $this->assertTrue($normalizer->supportsNormalization($dummy, ItemNormalizer::FORMAT)); @@ -120,6 +127,8 @@ public function testNormalize() $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $iriConverterProphecy->getIriFromItem($dummy)->willReturn('/dummies/10')->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), @@ -127,7 +136,8 @@ public function testNormalize() $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), new ReservedAttributeNameConverter(), - $resourceMetadataFactoryProphecy->reveal() + $resourceMetadataFactoryProphecy->reveal(), + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -156,6 +166,8 @@ public function testNormalizeIsNotAnArray() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->getResourceClass($object, null, true)->willReturn(\stdClass::class)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), @@ -163,7 +175,8 @@ public function testNormalizeIsNotAnArray() $resourceClassResolverProphecy->reveal(), $this->prophesize(PropertyAccessorInterface::class)->reveal(), new ReservedAttributeNameConverter(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); $normalizer->setSerializer(new Serializer([$normalizer])); @@ -200,6 +213,8 @@ public function testNormalizeThrowsNoSuchPropertyException() $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); $propertyAccessorProphecy->getValue($foo, 'bar')->willThrow(new NoSuchPropertyException()); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), @@ -207,7 +222,8 @@ public function testNormalizeThrowsNoSuchPropertyException() $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), new ReservedAttributeNameConverter(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); $normalizer->normalize($foo, ItemNormalizer::FORMAT); @@ -279,6 +295,9 @@ public function testDenormalize() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(NormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), @@ -286,7 +305,8 @@ public function testDenormalize() $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), new ReservedAttributeNameConverter(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -329,28 +349,41 @@ public function testDenormalizeUpdateOperationNotAllowed() $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Update is not allowed for this operation.'); + $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = new ItemNormalizer( $this->prophesize(PropertyNameCollectionFactoryInterface::class)->reveal(), $this->prophesize(PropertyMetadataFactoryInterface::class)->reveal(), - $this->prophesize(IriConverterInterface::class)->reveal(), + $iriConverterProphecy->reveal(), $this->prophesize(ResourceClassResolverInterface::class)->reveal(), - null, - null, - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(PropertyAccessorInterface::class)->reveal(), + new ReservedAttributeNameConverter(), + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); - $normalizer->denormalize( - [ - 'data' => [ - 'id' => 1, - 'type' => 'dummy', - ], - ], + $this->assertInstanceOf( Dummy::class, - ItemNormalizer::FORMAT, - [ - 'api_allow_update' => false, - ] + $normalizer->denormalize( + [ + 'data' => [ + 'type' => 'dummy', + 'attributes' => [ + 'id' => '/dummies/1', + 'name' => 'foo', + 'ghost' => 'invisible', + ], + ], + ], + Dummy::class, + ItemNormalizer::FORMAT, + [ + 'api_allow_update' => false, + ] + ) ); } @@ -380,6 +413,9 @@ public function testDenormalizeCollectionIsNotArray() ) )->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), @@ -387,7 +423,8 @@ public function testDenormalizeCollectionIsNotArray() $this->prophesize(ResourceClassResolverInterface::class)->reveal(), $this->prophesize(PropertyAccessorInterface::class)->reveal(), new ReservedAttributeNameConverter(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); $normalizer->denormalize( @@ -432,6 +469,9 @@ public function testDenormalizeCollectionWithInvalidKey() ) )->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), @@ -439,7 +479,8 @@ public function testDenormalizeCollectionWithInvalidKey() $this->prophesize(ResourceClassResolverInterface::class)->reveal(), $this->prophesize(PropertyAccessorInterface::class)->reveal(), new ReservedAttributeNameConverter(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); $normalizer->denormalize( @@ -486,6 +527,9 @@ public function testDenormalizeRelationIsNotResourceLinkage() $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(RelatedDummy::class)->willReturn(true)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), @@ -493,7 +537,8 @@ public function testDenormalizeRelationIsNotResourceLinkage() $resourceClassResolverProphecy->reveal(), $this->prophesize(PropertyAccessorInterface::class)->reveal(), new ReservedAttributeNameConverter(), - $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal() + $this->prophesize(ResourceMetadataFactoryInterface::class)->reveal(), + $identifiersExtractor->reveal() ); $normalizer->denormalize( diff --git a/tests/Serializer/AbstractItemNormalizerTest.php b/tests/Serializer/AbstractItemNormalizerTest.php index 420c6e5c898..a1e53a3b080 100644 --- a/tests/Serializer/AbstractItemNormalizerTest.php +++ b/tests/Serializer/AbstractItemNormalizerTest.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; @@ -51,6 +52,7 @@ public function testSupportNormalizationAndSupportDenormalization() $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true)->shouldBeCalled(); @@ -62,6 +64,11 @@ public function testSupportNormalizationAndSupportDenormalization() $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $this->assertTrue($normalizer->supportsNormalization($dummy)); @@ -131,12 +138,19 @@ public function testNormalize() $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo')->shouldBeCalled(); $serializerProphecy->normalize(['/dummies/2'], null, Argument::type('array'))->willReturn(['/dummies/2'])->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); $normalizer->setIgnoredAttributes(['alias']); @@ -199,12 +213,19 @@ public function testNormalizeReadableLinks() $serializerProphecy->normalize(['foo' => 'hello'], null, Argument::type('array'))->willReturn(['foo' => 'hello'])->shouldBeCalled(); $serializerProphecy->normalize([['foo' => 'hello']], null, Argument::type('array'))->willReturn([['foo' => 'hello']])->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -269,12 +290,20 @@ public function testDenormalize() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(NormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -341,12 +370,20 @@ public function testDenormalizeWritableLinks() ['bar' => 'baz'], RelatedDummy::class, null, Argument::type('array') )->willReturn($relatedDummy2)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -388,12 +425,20 @@ public function testBadRelationType() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -431,12 +476,20 @@ public function testInnerDocumentNotAllowed() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -465,12 +518,20 @@ public function testBadType() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -497,12 +558,20 @@ public function testJsonAllowIntAsFloat() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -546,12 +615,20 @@ public function testDenormalizeBadKeyType() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -581,12 +658,20 @@ public function testNullable() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -626,12 +711,19 @@ public function testChildInheritedProperty() $serializerProphecy->normalize('foo', null, Argument::type('array'))->willReturn('foo')->shouldBeCalled(); $serializerProphecy->normalize(null, null, Argument::type('array'))->willReturn(null)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), $resourceClassResolverProphecy->reveal(), $propertyAccessorProphecy->reveal(), + null, + null, + null, + false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -672,6 +764,9 @@ public function testDenormalizeRelationWithPlainId() $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); $itemDataProviderProphecy->getItem(RelatedDummy::class, 1, null, Argument::type('array'))->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), @@ -682,6 +777,7 @@ public function testDenormalizeRelationWithPlainId() null, $itemDataProviderProphecy->reveal(), true, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -722,6 +818,9 @@ public function testDoNotDenormalizeRelationWithPlainIdWhenPlainIdentifiersAreNo $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); $itemDataProviderProphecy->getItem(RelatedDummy::class, 1, null, Argument::type('array'))->shouldNotBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = $this->getMockForAbstractClass(AbstractItemNormalizer::class, [ $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), @@ -732,6 +831,7 @@ public function testDoNotDenormalizeRelationWithPlainIdWhenPlainIdentifiersAreNo null, $itemDataProviderProphecy->reveal(), false, + $identifiersExtractor->reveal(), ]); $normalizer->setSerializer($serializerProphecy->reveal()); diff --git a/tests/Serializer/ItemNormalizerTest.php b/tests/Serializer/ItemNormalizerTest.php index 93b4ab4faef..482e2ab690c 100644 --- a/tests/Serializer/ItemNormalizerTest.php +++ b/tests/Serializer/ItemNormalizerTest.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Tests\Serializer; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\ResourceClassResolverInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; @@ -47,11 +48,19 @@ public function testSupportNormalization() $resourceClassResolverProphecy->isResourceClass(Dummy::class)->willReturn(true)->shouldBeCalled(); $resourceClassResolverProphecy->isResourceClass(\stdClass::class)->willReturn(false)->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal() + $resourceClassResolverProphecy->reveal(), + null, + null, + null, + null, + false, + $identifiersExtractor->reveal() ); $this->assertTrue($normalizer->supportsNormalization($dummy)); @@ -86,11 +95,19 @@ public function testNormalize() $serializerProphecy->willImplement(NormalizerInterface::class); $serializerProphecy->normalize('hello', null, Argument::type('array'))->willReturn('hello')->shouldBeCalled(); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal() + $resourceClassResolverProphecy->reveal(), + null, + null, + null, + null, + false, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -116,11 +133,20 @@ public function testDenormalize() $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal() + $resourceClassResolverProphecy->reveal(), + null, + null, + null, + null, + false, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -140,18 +166,27 @@ public function testDenormalizeWithIri() $propertyMetadataFactoryProphecy->create(Dummy::class, 'name', [])->willReturn($propertyMetadataFactory)->shouldBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getItemFromIri('/dummies/12', ['resource_class' => Dummy::class, 'api_allow_update' => true, 'fetch_data' => true])->shouldBeCalled(); + $iriConverterProphecy->getItemFromIri('/dummies/12', ['resource_class' => Dummy::class, 'api_denormalize' => true, 'api_allow_update' => true, 'fetch_data' => true])->shouldBeCalled(); $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $serializerProphecy = $this->prophesize(SerializerInterface::class); $serializerProphecy->willImplement(DenormalizerInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $normalizer = new ItemNormalizer( $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal() + $resourceClassResolverProphecy->reveal(), + null, + null, + null, + null, + false, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); @@ -171,6 +206,9 @@ public function testDenormalizeWithIdAndUpdateNotAllowed() $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); + $identifiersExtractor = $this->prophesize(IdentifiersExtractorInterface::class); + $identifiersExtractor->getIdentifiersFromResourceClass(Dummy::class)->willReturn(['id'])->shouldBeCalled(); + $resourceClassResolverProphecy = $this->prophesize(ResourceClassResolverInterface::class); $serializerProphecy = $this->prophesize(SerializerInterface::class); @@ -180,9 +218,15 @@ public function testDenormalizeWithIdAndUpdateNotAllowed() $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $iriConverterProphecy->reveal(), - $resourceClassResolverProphecy->reveal() + $resourceClassResolverProphecy->reveal(), + null, + null, + null, + null, + false, + $identifiersExtractor->reveal() ); $normalizer->setSerializer($serializerProphecy->reveal()); - $normalizer->denormalize(['id' => '12', 'name' => 'hello'], Dummy::class, null, $context); + $normalizer->denormalize(['id' => '/dummies/12', 'name' => 'hello'], Dummy::class, null, $context); } }