From fc1196bd57b90f271aa74a00e4600ccb6f780ae0 Mon Sep 17 00:00:00 2001 From: abluchet Date: Wed, 29 Mar 2017 16:07:24 +0200 Subject: [PATCH] Implement ItemIdentifiersExtractor to cache identifiers properties per resource --- src/Api/CachedIdentifiersExtractor.php | 105 ++++++++++++++ src/Api/IdentifiersExtractor.php | 89 ++++++++++++ src/Api/IdentifiersExtractorInterface.php | 33 +++++ .../Symfony/Bundle/Resources/config/api.xml | 19 +++ src/Bridge/Symfony/Routing/IriConverter.php | 70 ++------- tests/Api/CachedIdentifiersExtractorTest.php | 135 ++++++++++++++++++ tests/Api/IdentifiersExtractorTest.php | 112 +++++++++++++++ .../ApiPlatformExtensionTest.php | 3 + tests/Fixtures/TestBundle/Entity/Dummy.php | 5 + .../TestBundle/Entity/RelatedDummy.php | 5 + 10 files changed, 519 insertions(+), 57 deletions(-) create mode 100644 src/Api/CachedIdentifiersExtractor.php create mode 100644 src/Api/IdentifiersExtractor.php create mode 100644 src/Api/IdentifiersExtractorInterface.php create mode 100644 tests/Api/CachedIdentifiersExtractorTest.php create mode 100644 tests/Api/IdentifiersExtractorTest.php diff --git a/src/Api/CachedIdentifiersExtractor.php b/src/Api/CachedIdentifiersExtractor.php new file mode 100644 index 00000000000..2110fa33ee3 --- /dev/null +++ b/src/Api/CachedIdentifiersExtractor.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Api; + +use ApiPlatform\Core\Util\ClassInfoTrait; +use Psr\Cache\CacheException; +use Psr\Cache\CacheItemPoolInterface; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +/** + * {@inheritdoc} + * + * @author Antoine Bluchet + */ +final class CachedIdentifiersExtractor implements IdentifiersExtractorInterface +{ + use ClassInfoTrait; + + const CACHE_KEY_PREFIX = 'iri_identifiers'; + + private $cacheItemPool; + private $propertyAccessor; + private $decorated; + + public function __construct(CacheItemPoolInterface $cacheItemPool, IdentifiersExtractorInterface $decorated, PropertyAccessorInterface $propertyAccessor = null) + { + $this->cacheItemPool = $cacheItemPool; + $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); + $this->decorated = $decorated; + } + + /** + * {@inheritdoc} + */ + public function getIdentifiersFromItem($item): array + { + $identifiers = []; + $resourceClass = $this->getObjectClass($item); + + $cacheKey = self::CACHE_KEY_PREFIX.md5($resourceClass); + + // This is to avoid setting the cache twice in the case where the related item cache doesn't exist + $cacheIsHit = false; + + try { + $cacheItem = $this->cacheItemPool->getItem($cacheKey); + $isRelationCached = true; + + if ($cacheIsHit = $cacheItem->isHit()) { + foreach ($cacheItem->get() as $propertyName) { + $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); + + if (!is_object($identifiers[$propertyName])) { + continue; + } + + $relatedItem = $identifiers[$propertyName]; + $relatedCacheKey = self::CACHE_KEY_PREFIX.md5($this->getObjectClass($relatedItem)); + + $relatedCacheItem = $this->cacheItemPool->getItem($relatedCacheKey); + + if (!$relatedCacheItem->isHit()) { + $isRelationCached = false; + break; + } + + unset($identifiers[$propertyName]); + + $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedCacheItem->get()[0]); + } + + if (true === $isRelationCached) { + return $identifiers; + } + } + } catch (CacheException $e) { + // do nothing + } + + $identifiers = $this->decorated->getIdentifiersFromItem($item); + + if (isset($cacheItem) && false === $cacheIsHit) { + try { + $cacheItem->set(array_keys($identifiers)); + $this->cacheItemPool->save($cacheItem); + } catch (CacheException $e) { + // do nothing + } + } + + return $identifiers; + } +} diff --git a/src/Api/IdentifiersExtractor.php b/src/Api/IdentifiersExtractor.php new file mode 100644 index 00000000000..3abb0fc8ac4 --- /dev/null +++ b/src/Api/IdentifiersExtractor.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Api; + +use ApiPlatform\Core\Exception\RuntimeException; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Core\Util\ClassInfoTrait; +use Symfony\Component\PropertyAccess\PropertyAccess; +use Symfony\Component\PropertyAccess\PropertyAccessorInterface; + +/** + * {@inheritdoc} + * + * @author Antoine Bluchet + */ +final class IdentifiersExtractor implements IdentifiersExtractorInterface +{ + use ClassInfoTrait; + + private $propertyNameCollectionFactory; + private $propertyMetadataFactory; + private $propertyAccessor; + + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, PropertyAccessorInterface $propertyAccessor = null) + { + $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; + $this->propertyMetadataFactory = $propertyMetadataFactory; + $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); + } + + /** + * {@inheritdoc} + */ + public function getIdentifiersFromItem($item): array + { + $identifiers = []; + $resourceClass = $this->getObjectClass($item); + + foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { + $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); + + $identifier = $propertyMetadata->isIdentifier(); + if (null === $identifier || false === $identifier) { + continue; + } + + $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); + + if (!is_object($identifiers[$propertyName])) { + continue; + } + + $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]); + $relatedItem = $identifiers[$propertyName]; + + unset($identifiers[$propertyName]); + + foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) { + $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName); + + if ($propertyMetadata->isIdentifier()) { + if (isset($identifiers[$propertyName])) { + throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); + } + + $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName); + } + } + + if (!isset($identifiers[$propertyName])) { + throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); + } + } + + return $identifiers; + } +} diff --git a/src/Api/IdentifiersExtractorInterface.php b/src/Api/IdentifiersExtractorInterface.php new file mode 100644 index 00000000000..4d0eb6b1589 --- /dev/null +++ b/src/Api/IdentifiersExtractorInterface.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Api; + +/** + * Extracts identifiers for a given Resource according to the retrieved Metadata. + * + * @author Antoine Bluchet + */ +interface IdentifiersExtractorInterface +{ + /** + * Finds identifiers from an Item (object). + * + * @param object $item + * + * @throws RuntimeException + * + * @return array + */ + public function getIdentifiersFromItem($item): array; +} diff --git a/src/Bridge/Symfony/Bundle/Resources/config/api.xml b/src/Bridge/Symfony/Bundle/Resources/config/api.xml index 254c5c690b0..6ba8243a4d7 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/api.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/api.xml @@ -51,6 +51,7 @@ + @@ -165,11 +166,29 @@ %api_platform.exception_to_status% + + + + + + + + + + + + + + + + + + diff --git a/src/Bridge/Symfony/Routing/IriConverter.php b/src/Bridge/Symfony/Routing/IriConverter.php index 69057834cef..82a48f39a58 100644 --- a/src/Bridge/Symfony/Routing/IriConverter.php +++ b/src/Bridge/Symfony/Routing/IriConverter.php @@ -13,12 +13,13 @@ namespace ApiPlatform\Core\Bridge\Symfony\Routing; +use ApiPlatform\Core\Api\IdentifiersExtractor; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; use ApiPlatform\Core\Api\IriConverterInterface; use ApiPlatform\Core\Api\UrlGeneratorInterface; use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Exception\ItemNotFoundException; -use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; use ApiPlatform\Core\Util\ClassInfoTrait; @@ -42,15 +43,23 @@ final class IriConverter implements IriConverterInterface private $routeNameResolver; private $router; private $propertyAccessor; + private $identifiersExtractor; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ItemDataProviderInterface $itemDataProvider, RouteNameResolverInterface $routeNameResolver, RouterInterface $router, PropertyAccessorInterface $propertyAccessor = null) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ItemDataProviderInterface $itemDataProvider, RouteNameResolverInterface $routeNameResolver, RouterInterface $router, PropertyAccessorInterface $propertyAccessor = null, IdentifiersExtractorInterface $identifiersExtractor = null) { $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; $this->propertyMetadataFactory = $propertyMetadataFactory; $this->itemDataProvider = $itemDataProvider; $this->routeNameResolver = $routeNameResolver; $this->router = $router; - $this->propertyAccessor = $propertyAccessor ?: PropertyAccess::createPropertyAccessor(); + $this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor(); + + if (null === $identifiersExtractor) { + @trigger_error('Not injecting ItemIdentifiersExtractor is deprecated since API Platform 2.1 and will not be possible anymore in API Platform 3'); + $this->identifiersExtractor = new IdentifiersExtractor($this->propertyNameCollectionFactory, $this->propertyMetadataFactory, $this->propertyAccessor); + } else { + $this->identifiersExtractor = $identifiersExtractor; + } } /** @@ -83,7 +92,7 @@ public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface $resourceClass = $this->getObjectClass($item); $routeName = $this->routeNameResolver->getRouteName($resourceClass, false); - $identifiers = $this->generateIdentifiersUrl($this->getIdentifiersFromItem($item)); + $identifiers = $this->generateIdentifiersUrl($this->identifiersExtractor->getIdentifiersFromItem($item)); return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType); } @@ -100,59 +109,6 @@ public function getIriFromResourceClass(string $resourceClass, int $referenceTyp } } - /** - * Find identifiers from an Item (Object). - * - * @param object $item - * - * @throws RuntimeException - * - * @return array - */ - private function getIdentifiersFromItem($item): array - { - $identifiers = []; - $resourceClass = $this->getObjectClass($item); - - foreach ($this->propertyNameCollectionFactory->create($resourceClass) as $propertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $propertyName); - - $identifier = $propertyMetadata->isIdentifier(); - if (null === $identifier || false === $identifier) { - continue; - } - - $identifiers[$propertyName] = $this->propertyAccessor->getValue($item, $propertyName); - - if (!is_object($identifiers[$propertyName])) { - continue; - } - - $relatedResourceClass = $this->getObjectClass($identifiers[$propertyName]); - $relatedItem = $identifiers[$propertyName]; - - unset($identifiers[$propertyName]); - - foreach ($this->propertyNameCollectionFactory->create($relatedResourceClass) as $relatedPropertyName) { - $propertyMetadata = $this->propertyMetadataFactory->create($relatedResourceClass, $relatedPropertyName); - - if ($propertyMetadata->isIdentifier()) { - if (isset($identifiers[$propertyName])) { - throw new RuntimeException(sprintf('Composite identifiers not supported in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); - } - - $identifiers[$propertyName] = $this->propertyAccessor->getValue($relatedItem, $relatedPropertyName); - } - } - - if (!isset($identifiers[$propertyName])) { - throw new RuntimeException(sprintf('No identifier found in "%s" through relation "%s" of "%s" used as identifier', $relatedResourceClass, $propertyName, $resourceClass)); - } - } - - return $identifiers; - } - /** * Generate the identifier url. * diff --git a/tests/Api/CachedIdentifiersExtractorTest.php b/tests/Api/CachedIdentifiersExtractorTest.php new file mode 100644 index 00000000000..0b69ba50002 --- /dev/null +++ b/tests/Api/CachedIdentifiersExtractorTest.php @@ -0,0 +1,135 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\Api; + +use ApiPlatform\Core\Api\CachedIdentifiersExtractor; +use ApiPlatform\Core\Api\IdentifiersExtractorInterface; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; +use Psr\Cache\CacheItemInterface; +use Psr\Cache\CacheItemPoolInterface; + +/** + * @author Antoine Bluchet + */ +class CachedIdentifiersExtractorTest extends \PHPUnit_Framework_TestCase +{ + public function testFirstPass() + { + $key = 'iri_identifiers'.md5(Dummy::class); + + $cacheItem = $this->prophesize(CacheItemInterface::class); + $cacheItem->isHit()->shouldBeCalled()->willReturn(false); + $cacheItem->set(['id'])->shouldBeCalled(); + + $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); + $cacheItemPool->getItem($key)->shouldBeCalled()->willReturn($cacheItem); + $cacheItemPool->save($cacheItem)->shouldBeCalled(); + + $dummy = new Dummy(); + $dummy->setId(1); + + $decoration = $this->prophesize(IdentifiersExtractorInterface::class); + $decoration->getIdentifiersFromItem($dummy)->shouldBeCalled()->willReturn(['id' => 1]); + + $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); + + $this->assertEquals(['id' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); + } + + public function testSecondPass() + { + $key = 'iri_identifiers'.md5(Dummy::class); + + $cacheItem = $this->prophesize(CacheItemInterface::class); + $cacheItem->isHit()->shouldBeCalled()->willReturn(true); + $cacheItem->get()->shouldBeCalled()->willReturn(['id']); + + $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); + $cacheItemPool->getItem($key)->shouldBeCalled()->willReturn($cacheItem); + + $dummy = new Dummy(); + $dummy->setId(1); + + $decoration = $this->prophesize(IdentifiersExtractorInterface::class); + $decoration->getIdentifiersFromItem($dummy)->shouldNotBeCalled(); + + $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); + + $this->assertEquals(['id' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); + } + + public function testSecondPassWithRelatedNotCached() + { + $key = 'iri_identifiers'.md5(Dummy::class); + $keyRelated = 'iri_identifiers'.md5(RelatedDummy::class); + + $cacheItem = $this->prophesize(CacheItemInterface::class); + $cacheItem->isHit()->shouldBeCalled()->willReturn(true); + $cacheItem->get()->shouldBeCalled()->willReturn(['id', 'relatedDummy']); + + $cacheItemRelated = $this->prophesize(CacheItemInterface::class); + $cacheItemRelated->isHit()->shouldBeCalled()->willReturn(false); + + $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); + $cacheItemPool->getItem($key)->shouldBeCalled()->willReturn($cacheItem); + $cacheItemPool->getItem($keyRelated)->shouldBeCalled()->willReturn($cacheItemRelated); + + $related = new RelatedDummy(); + $related->setId(1); + + $dummy = new Dummy(); + $dummy->setId(1); + $dummy->setRelatedDummy($related); + + $decoration = $this->prophesize(IdentifiersExtractorInterface::class); + $decoration->getIdentifiersFromItem($dummy)->shouldBeCalled()->willReturn(['id' => 1, 'relatedDummy' => 1]); + + $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); + + $this->assertEquals(['id' => 1, 'relatedDummy' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); + } + + public function testSecondPassWithRelatedCached() + { + $key = 'iri_identifiers'.md5(Dummy::class); + $keyRelated = 'iri_identifiers'.md5(RelatedDummy::class); + + $cacheItem = $this->prophesize(CacheItemInterface::class); + $cacheItem->isHit()->shouldBeCalled()->willReturn(true); + $cacheItem->get()->shouldBeCalled()->willReturn(['id', 'relatedDummy']); + + $cacheItemRelated = $this->prophesize(CacheItemInterface::class); + $cacheItemRelated->isHit()->shouldBeCalled()->willReturn(true); + $cacheItemRelated->get()->shouldBeCalled()->willReturn(['id']); + + $cacheItemPool = $this->prophesize(CacheItemPoolInterface::class); + $cacheItemPool->getItem($key)->shouldBeCalled()->willReturn($cacheItem); + $cacheItemPool->getItem($keyRelated)->shouldBeCalled()->willReturn($cacheItemRelated); + + $related = new RelatedDummy(); + $related->setId(1); + + $dummy = new Dummy(); + $dummy->setId(1); + $dummy->setRelatedDummy($related); + + $decoration = $this->prophesize(IdentifiersExtractorInterface::class); + $decoration->getIdentifiersFromItem($dummy)->shouldNotBeCalled(); + + $identifiersExtractor = new CachedIdentifiersExtractor($cacheItemPool->reveal(), $decoration->reveal(), null); + + $this->assertEquals(['id' => 1, 'relatedDummy' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); + } +} diff --git a/tests/Api/IdentifiersExtractorTest.php b/tests/Api/IdentifiersExtractorTest.php new file mode 100644 index 00000000000..d07c3ec7201 --- /dev/null +++ b/tests/Api/IdentifiersExtractorTest.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace ApiPlatform\Core\Tests\Api; + +use ApiPlatform\Core\Api\IdentifiersExtractor; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Property\PropertyMetadata; +use ApiPlatform\Core\Metadata\Property\PropertyNameCollection; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; + +/** + * @author Antoine Bluchet + */ +class IdentifiersExtractorTest extends \PHPUnit_Framework_TestCase +{ + private function getMetadataFactoryProphecies($class, $identifiers, array $prophecies = null) + { + //adds a random property that is not an identifier + $properties = array_merge(['foo'], $identifiers); + + if (!$prophecies) { + $prophecies = [$this->prophesize(PropertyNameCollectionFactoryInterface::class), $this->prophesize(PropertyMetadataFactoryInterface::class)]; + } + + list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $prophecies; + + $propertyNameCollectionFactoryProphecy->create($class)->shouldBeCalled()->willReturn(new PropertyNameCollection($properties)); + + foreach ($properties as $prop) { + $metadata = new PropertyMetadata(); + $propertyMetadataFactoryProphecy->create($class, $prop)->shouldBeCalled()->willReturn($metadata->withIdentifier(in_array($prop, $identifiers, true))); + } + + return [$propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy]; + } + + public function testGetIdentifiersFromItem() + { + list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(Dummy::class, ['id']); + + $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); + + $dummy = new Dummy(); + $dummy->setId(1); + + $this->assertEquals(['id' => 1], $identifiersExtractor->getIdentifiersFromItem($dummy)); + } + + public function testGetCompositeIdentifiersFromItem() + { + list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'name']); + + $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); + + $dummy = new Dummy(); + $dummy->setId(1); + $dummy->setName('foo'); + + $this->assertEquals(['id' => 1, 'name' => 'foo'], $identifiersExtractor->getIdentifiersFromItem($dummy)); + } + + public function testGetRelatedIdentifiersFromItem() + { + $prophecies = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'relatedDummy']); + list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(RelatedDummy::class, ['id'], $prophecies); + + $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); + + $related = new RelatedDummy(); + $related->setId(2); + + $dummy = new Dummy(); + $dummy->setId(1); + $dummy->setRelatedDummy($related); + + $this->assertEquals(['id' => 1, 'relatedDummy' => 2], $identifiersExtractor->getIdentifiersFromItem($dummy)); + } + + /** + * @expectedException \ApiPlatform\Core\Exception\RuntimeException + * @expectedMessage No identifier found in "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy" through relation "relatedDummy" of "ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy" used as identifier + */ + public function testThrowNoIdentifierFromItem() + { + $prophecies = $this->getMetadataFactoryProphecies(Dummy::class, ['id', 'relatedDummy']); + list($propertyNameCollectionFactoryProphecy, $propertyMetadataFactoryProphecy) = $this->getMetadataFactoryProphecies(RelatedDummy::class, [], $prophecies); + + $identifiersExtractor = new IdentifiersExtractor($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); + + $related = new RelatedDummy(); + $related->setId(2); + + $dummy = new Dummy(); + $dummy->setId(1); + $dummy->setRelatedDummy($related); + + $identifiersExtractor->getIdentifiersFromItem($dummy); + } +} diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 8d33b1ac473..c519766868a 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -275,6 +275,7 @@ private function getContainerBuilderProphecy() 'api_platform.action.exception', 'api_platform.action.placeholder', 'api_platform.cache.metadata.property', + 'api_platform.cache.identifiers_extractor', 'api_platform.cache.metadata.resource', 'api_platform.cache.route_name_resolver', 'api_platform.collection_data_provider', @@ -361,6 +362,8 @@ private function getContainerBuilderProphecy() 'api_platform.metadata.resource.name_collection_factory.cached', 'api_platform.metadata.resource.name_collection_factory.xml', 'api_platform.metadata.resource.name_collection_factory.yaml', + 'api_platform.identifiers_extractor', + 'api_platform.identifiers_extractor.cached', 'api_platform.negotiator', 'api_platform.operation_method_resolver', 'api_platform.operation_path_resolver.custom', diff --git a/tests/Fixtures/TestBundle/Entity/Dummy.php b/tests/Fixtures/TestBundle/Entity/Dummy.php index 7f997077cdb..25170b52f68 100644 --- a/tests/Fixtures/TestBundle/Entity/Dummy.php +++ b/tests/Fixtures/TestBundle/Entity/Dummy.php @@ -157,6 +157,11 @@ public function getId() return $this->id; } + public function setId($id) + { + $this->id = $id; + } + public function setName($name) { $this->name = $name; diff --git a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php index 47f730432b1..fb024a4fdbb 100644 --- a/tests/Fixtures/TestBundle/Entity/RelatedDummy.php +++ b/tests/Fixtures/TestBundle/Entity/RelatedDummy.php @@ -90,6 +90,11 @@ public function getId() return $this->id; } + public function setId($id) + { + $this->id = $id; + } + public function setName($name) { $this->name = $name;