From 7f82fd7f96bbb855599de275ffe940c63156fc5d Mon Sep 17 00:00:00 2001 From: soyuka Date: Thu, 3 Nov 2016 08:45:41 +0100 Subject: [PATCH] getItemFromIri now takes an optional context as third argument --- src/Api/IriConverterInterface.php | 4 +- .../Orm/Extension/EagerLoadingExtension.php | 91 ++++++++- .../Extension/QueryItemExtensionInterface.php | 3 +- src/Bridge/Doctrine/Orm/ItemDataProvider.php | 7 +- .../ApiPlatformExtension.php | 3 + .../DependencyInjection/Configuration.php | 9 + .../Bundle/Resources/config/doctrine_orm.xml | 4 + src/Bridge/Symfony/Routing/IriConverter.php | 4 +- src/DataProvider/ChainItemDataProvider.php | 4 +- .../ItemDataProviderInterface.php | 4 +- src/EventListener/ReadListener.php | 2 +- src/JsonLd/Serializer/ItemNormalizer.php | 2 +- src/Serializer/AbstractItemNormalizer.php | 2 +- src/Serializer/ItemNormalizer.php | 2 +- .../Extension/EagerLoadingExtensionTest.php | 178 +++++++++++++++++- .../Doctrine/Orm/ItemDataProviderTest.php | 9 +- .../ApiPlatformExtensionTest.php | 3 + .../DependencyInjection/ConfigurationTest.php | 5 + .../Symfony/Routing/IriConverterTest.php | 6 +- .../ChainItemDataProviderTest.php | 8 +- tests/EventListener/ReadListenerTest.php | 4 +- .../Serializer/AbstractItemNormalizerTest.php | 4 +- 22 files changed, 313 insertions(+), 45 deletions(-) diff --git a/src/Api/IriConverterInterface.php b/src/Api/IriConverterInterface.php index f0e29ddc681..dc0d0c1c4f4 100644 --- a/src/Api/IriConverterInterface.php +++ b/src/Api/IriConverterInterface.php @@ -25,13 +25,13 @@ interface IriConverterInterface * Retrieves an item from its IRI. * * @param string $iri - * @param bool $fetchData + * @param array $context * * @throws InvalidArgumentException * * @return object */ - public function getItemFromIri(string $iri, bool $fetchData = false); + public function getItemFromIri(string $iri, array $context = []); /** * Gets the IRI associated with the given item. diff --git a/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php b/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php index 7a7d800258c..55b358ac6d2 100644 --- a/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php +++ b/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php @@ -12,8 +12,10 @@ namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Extension; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; +use ApiPlatform\Core\Exception\RuntimeException; use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Property\Factory\PropertyNameCollectionFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\QueryBuilder; @@ -28,11 +30,47 @@ final class EagerLoadingExtension implements QueryCollectionExtensionInterface, { private $propertyNameCollectionFactory; private $propertyMetadataFactory; + private $resourceMetadataFactory; + private $enabled; + private $maxJoins; + private $eagerOnly; - public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory) + public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, ResourceMetadataFactoryInterface $resourceMetadataFactory, bool $enabled = true, int $maxJoins = 30, bool $eagerOnly = true) { $this->propertyMetadataFactory = $propertyMetadataFactory; $this->propertyNameCollectionFactory = $propertyNameCollectionFactory; + $this->resourceMetadataFactory = $resourceMetadataFactory; + $this->enabled = $enabled; + $this->maxJoins = $maxJoins; + $this->eagerOnly = $eagerOnly; + } + + /** + * Gets serializer groups once if available, if not it returns the $options array. + * + * @param array $options represents the operation name so that groups are the one of the specific operation + * @param string $resourceClass + * @param string $context normalization_context or denormalization_context + * + * @return string[] + */ + private function getSerializerGroups(string $resourceClass, array $options, string $context): array + { + $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); + + if (isset($options['collection_operation_name'])) { + $context = $resourceMetadata->getCollectionOperationAttribute($options['collection_operation_name'], $context, null, true); + } elseif (isset($options['item_operation_name'])) { + $context = $resourceMetadata->getItemOperationAttribute($options['item_operation_name'], $context, null, true); + } else { + $context = $resourceMetadata->getAttribute($context); + } + + if (empty($context['groups'])) { + return $options; + } + + return ['serializer_groups' => $context['groups']]; } /** @@ -40,23 +78,46 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName */ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) { + if (false === $this->enabled) { + return; + } + + $options = []; + if (null !== $operationName) { - $propertyMetadataOptions = ['collection_operation_name' => $operationName]; + $options = ['collection_operation_name' => $operationName]; } - $this->joinRelations($queryBuilder, $resourceClass, $propertyMetadataOptions ?? []); + $groups = $this->getSerializerGroups($resourceClass, $options, 'normalization_context'); + + $this->joinRelations($queryBuilder, $resourceClass, $groups); } /** * {@inheritdoc} + * The context may contain serialization groups which helps defining joined entities that are readable. */ - public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null) + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []) { + if (false === $this->enabled) { + return; + } + + $options = []; + if (null !== $operationName) { - $propertyMetadataOptions = ['item_operation_name' => $operationName]; + $options = ['item_operation_name' => $operationName]; + } + + if (isset($context['groups'])) { + $groups = ['serializer_groups' => $context['groups']]; + } elseif (isset($context['resource_class'])) { + $groups = $this->getSerializerGroups($context['resource_class'], $options, isset($context['api_denormalize']) ? 'denormalization_context' : 'normalization_context'); + } else { + $groups = $this->getSerializerGroups($resourceClass, $options, 'normalization_context'); } - $this->joinRelations($queryBuilder, $resourceClass, $propertyMetadataOptions ?? []); + $this->joinRelations($queryBuilder, $resourceClass, $groups); } /** @@ -68,9 +129,16 @@ public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterf * @param string $originAlias the current entity alias (first o, then a1, a2 etc.) * @param string $relationAlias the previous relation alias to keep it unique * @param bool $wasLeftJoin if the relation containing the new one had a left join, we have to force the new one to left join too + * @param int $joinCount the number of joins + * + * @throws RuntimeException when the max number of joins has been reached */ - private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass, array $propertyMetadataOptions = [], string $originAlias = 'o', string &$relationAlias = 'a', bool $wasLeftJoin = false) + private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass, array $propertyMetadataOptions = [], string $originAlias = 'o', string &$relationAlias = 'a', bool $wasLeftJoin = false, int &$joinCount = 0) { + if ($joinCount > $this->maxJoins) { + throw new RuntimeException('The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary.'); + } + $entityManager = $queryBuilder->getEntityManager(); $classMetadata = $entityManager->getClassMetadata($resourceClass); $j = 0; @@ -79,7 +147,11 @@ private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass foreach ($classMetadata->associationMappings as $association => $mapping) { $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $association, $propertyMetadataOptions); - if (ClassMetadataInfo::FETCH_EAGER !== $mapping['fetch'] || false === $propertyMetadata->isReadableLink()) { + if (true === $this->eagerOnly && ClassMetadataInfo::FETCH_EAGER !== $mapping['fetch']) { + continue; + } + + if (false === $propertyMetadata->isReadableLink() || false === $propertyMetadata->isReadable()) { continue; } @@ -97,6 +169,7 @@ private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass $associationAlias = $relationAlias.$i++; $queryBuilder->{$method}($originAlias.'.'.$association, $associationAlias); + ++$joinCount; $select = []; $targetClassMetadata = $entityManager->getClassMetadata($mapping['targetEntity']); @@ -118,7 +191,7 @@ private function joinRelations(QueryBuilder $queryBuilder, string $resourceClass $relationAlias .= ++$j; - $this->joinRelations($queryBuilder, $mapping['targetEntity'], $propertyMetadataOptions, $associationAlias, $relationAlias, $method === 'leftJoin'); + $this->joinRelations($queryBuilder, $mapping['targetEntity'], $propertyMetadataOptions, $associationAlias, $relationAlias, $method === 'leftJoin', $joinCount); } } } diff --git a/src/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php b/src/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php index e9e2185cb84..ebb32fb5a2c 100644 --- a/src/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php +++ b/src/Bridge/Doctrine/Orm/Extension/QueryItemExtensionInterface.php @@ -28,6 +28,7 @@ interface QueryItemExtensionInterface * @param string $resourceClass * @param array $identifiers * @param string|null $operationName + * @param array $context */ - public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null); + public function applyToItem(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, array $identifiers, string $operationName = null, array $context = []); } diff --git a/src/Bridge/Doctrine/Orm/ItemDataProvider.php b/src/Bridge/Doctrine/Orm/ItemDataProvider.php index 141a493860c..480cbc91db4 100644 --- a/src/Bridge/Doctrine/Orm/ItemDataProvider.php +++ b/src/Bridge/Doctrine/Orm/ItemDataProvider.php @@ -55,9 +55,11 @@ public function __construct(ManagerRegistry $managerRegistry, PropertyNameCollec /** * {@inheritdoc} * + * The context may contain a `fetch_data` key representing whether the value should be fetched by Doctrine or if we should return a reference. + * * @throws RuntimeException */ - public function getItem(string $resourceClass, $id, string $operationName = null, bool $fetchData = false) + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) { $manager = $this->managerRegistry->getManagerForClass($resourceClass); if (null === $manager) { @@ -66,6 +68,7 @@ public function getItem(string $resourceClass, $id, string $operationName = null $identifiers = $this->normalizeIdentifiers($id, $manager, $resourceClass); + $fetchData = $context['fetch_data'] ?? false; if (!$fetchData && $manager instanceof EntityManagerInterface) { return $manager->getReference($resourceClass, $identifiers); } @@ -81,7 +84,7 @@ public function getItem(string $resourceClass, $id, string $operationName = null $this->addWhereForIdentifiers($identifiers, $queryBuilder); foreach ($this->itemExtensions as $extension) { - $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName); + $extension->applyToItem($queryBuilder, $queryNameGenerator, $resourceClass, $identifiers, $operationName, $context); if ($extension instanceof QueryResultItemExtensionInterface && $extension->supportsResult($resourceClass, $operationName)) { return $extension->getResult($queryBuilder); diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php index d4779732fc2..1d371b98807 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtension.php @@ -90,6 +90,9 @@ private function handleConfig(ContainerBuilder $container, array $config, array $container->setParameter('api_platform.exception_to_status', $config['exception_to_status']); $container->setParameter('api_platform.formats', $formats); $container->setParameter('api_platform.error_formats', $errorFormats); + $container->setParameter('api_platform.eager_loading.enabled', $config['eager_loading']['enabled']); + $container->setParameter('api_platform.eager_loading.max_joins', $config['eager_loading']['max_joins']); + $container->setParameter('api_platform.eager_loading.eager_only', $config['eager_loading']['eager_only']); $container->setParameter('api_platform.collection.order', $config['collection']['order']); $container->setParameter('api_platform.collection.order_parameter_name', $config['collection']['order_parameter_name']); $container->setParameter('api_platform.collection.pagination.enabled', $config['collection']['pagination']['enabled']); diff --git a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php index e59bfb665da..43cba21319c 100644 --- a/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php +++ b/src/Bridge/Symfony/Bundle/DependencyInjection/Configuration.php @@ -42,6 +42,15 @@ public function getConfigTreeBuilder() ->scalarNode('version')->defaultValue('0.0.0')->info('The version of the API.')->end() ->scalarNode('default_operation_path_resolver')->defaultValue('api_platform.operation_path_resolver.underscore')->info('Specify the default operation path resolver to use for generating resources operations path.')->end() ->scalarNode('name_converter')->defaultNull()->info('Specify a name converter to use.')->end() + ->arrayNode('eager_loading') + ->canBeDisabled() + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('enabled')->defaultTrue()->info('To enable or disable eager loading')->end() + ->integerNode('max_joins')->defaultValue(30)->info('Max number of joined relations before EagerLoading throws a RuntimeException')->end() + ->booleanNode('eager_only')->defaultTrue()->info('Only eager load relations having an EAGER fetch mode')->end() + ->end() + ->end() ->booleanNode('enable_fos_user')->defaultValue(false)->info('Enable the FOSUserBundle integration.')->end() ->booleanNode('enable_nelmio_api_doc')->defaultValue(false)->info('Enable the Nelmio Api doc integration.')->end() ->booleanNode('enable_swagger')->defaultValue(true)->info('Enable the Swagger documentation and export.')->end() diff --git a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml index 756e958949a..4b7348e76fb 100644 --- a/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml +++ b/src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml @@ -90,6 +90,10 @@ + + %api_platform.eager_loading.enabled% + %api_platform.eager_loading.max_joins% + %api_platform.eager_loading.eager_only% diff --git a/src/Bridge/Symfony/Routing/IriConverter.php b/src/Bridge/Symfony/Routing/IriConverter.php index 55147077a90..5ce015a3b6f 100644 --- a/src/Bridge/Symfony/Routing/IriConverter.php +++ b/src/Bridge/Symfony/Routing/IriConverter.php @@ -53,7 +53,7 @@ public function __construct(PropertyNameCollectionFactoryInterface $propertyName /** * {@inheritdoc} */ - public function getItemFromIri(string $iri, bool $fetchData = false) + public function getItemFromIri(string $iri, array $context = []) { try { $parameters = $this->router->match($iri); @@ -65,7 +65,7 @@ public function getItemFromIri(string $iri, bool $fetchData = false) throw new InvalidArgumentException(sprintf('No resource associated to "%s".', $iri)); } - if ($item = $this->itemDataProvider->getItem($parameters['_api_resource_class'], $parameters['id'], null, $fetchData)) { + if ($item = $this->itemDataProvider->getItem($parameters['_api_resource_class'], $parameters['id'], null, $context)) { return $item; } diff --git a/src/DataProvider/ChainItemDataProvider.php b/src/DataProvider/ChainItemDataProvider.php index 7e3a1a25298..edaa194ba81 100644 --- a/src/DataProvider/ChainItemDataProvider.php +++ b/src/DataProvider/ChainItemDataProvider.php @@ -33,11 +33,11 @@ public function __construct(array $dataProviders) /** * {@inheritdoc} */ - public function getItem(string $resourceClass, $id, string $operationName = null, bool $fetchData = false) + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) { foreach ($this->dataProviders as $dataProviders) { try { - return $dataProviders->getItem($resourceClass, $id, $operationName, $fetchData); + return $dataProviders->getItem($resourceClass, $id, $operationName, $context); } catch (ResourceClassNotSupportedException $e) { continue; } diff --git a/src/DataProvider/ItemDataProviderInterface.php b/src/DataProvider/ItemDataProviderInterface.php index b0ff3d543a1..7de63c992c6 100644 --- a/src/DataProvider/ItemDataProviderInterface.php +++ b/src/DataProvider/ItemDataProviderInterface.php @@ -26,11 +26,11 @@ interface ItemDataProviderInterface * @param string $resourceClass * @param string|null $operationName * @param int|string $id - * @param bool $fetchData + * @param array $context * * @throws ResourceClassNotSupportedException * * @return object|null */ - public function getItem(string $resourceClass, $id, string $operationName = null, bool $fetchData = false); + public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []); } diff --git a/src/EventListener/ReadListener.php b/src/EventListener/ReadListener.php index 8c2a287373b..d9ad30e3244 100644 --- a/src/EventListener/ReadListener.php +++ b/src/EventListener/ReadListener.php @@ -91,7 +91,7 @@ private function getCollectionData(Request $request, array $attributes) private function getItemData(Request $request, array $attributes) { $id = $request->attributes->get('id'); - $data = $this->itemDataProvider->getItem($attributes['resource_class'], $id, $attributes['item_operation_name'], true); + $data = $this->itemDataProvider->getItem($attributes['resource_class'], $id, $attributes['item_operation_name'], ['fetch_data' => true]); if (null === $data) { throw new NotFoundHttpException('Not Found'); diff --git a/src/JsonLd/Serializer/ItemNormalizer.php b/src/JsonLd/Serializer/ItemNormalizer.php index af206c1cc5b..db5949a7bef 100644 --- a/src/JsonLd/Serializer/ItemNormalizer.php +++ b/src/JsonLd/Serializer/ItemNormalizer.php @@ -95,7 +95,7 @@ public function denormalize($data, $class, $format = null, array $context = []) throw new InvalidArgumentException('Update is not allowed for this operation.'); } - $context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['@id'], true); + $context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['@id'], ['fetch_data' => true] + $context); } return parent::denormalize($data, $class, $format, $context); diff --git a/src/Serializer/AbstractItemNormalizer.php b/src/Serializer/AbstractItemNormalizer.php index 5eda2c18f6d..cd8efe54d47 100644 --- a/src/Serializer/AbstractItemNormalizer.php +++ b/src/Serializer/AbstractItemNormalizer.php @@ -272,7 +272,7 @@ private function denormalizeRelation(string $attributeName, PropertyMetadata $pr { if (is_string($value)) { try { - return $this->iriConverter->getItemFromIri($value, true); + return $this->iriConverter->getItemFromIri($value, ['fetch_data' => true] + $context); } catch (InvalidArgumentException $e) { // Give a chance to other normalizers (e.g.: DateTimeNormalizer) } diff --git a/src/Serializer/ItemNormalizer.php b/src/Serializer/ItemNormalizer.php index acf029a6c01..391eb5e3982 100644 --- a/src/Serializer/ItemNormalizer.php +++ b/src/Serializer/ItemNormalizer.php @@ -33,7 +33,7 @@ public function denormalize($data, $class, $format = null, array $context = []) throw new InvalidArgumentException('Update is not allowed for this operation.'); } - $context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['id'], true); + $context['object_to_populate'] = $this->iriConverter->getItemFromIri($data['id'], ['fetch_data' => true] + $context); } return parent::denormalize($data, $class, $format, $context); diff --git a/tests/Bridge/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php b/tests/Bridge/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php index 4db34e85954..93f1419a156 100644 --- a/tests/Bridge/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php +++ b/tests/Bridge/Doctrine/Orm/Extension/EagerLoadingExtensionTest.php @@ -13,24 +13,32 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\EagerLoadingExtension; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGenerator; +use ApiPlatform\Core\Exception\RuntimeException; 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\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Dummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\UnknownDummy; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\QueryBuilder; +use Prophecy\Argument; /** * @author Amrouche Hamza + * @author Antoine Bluchet */ class EagerLoadingExtensionTest extends \PHPUnit_Framework_TestCase { public function testApplyToCollection() { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $relatedNameCollection = new PropertyNameCollection(['id', 'name', 'notindatabase', 'notreadable']); @@ -88,12 +96,15 @@ public function testApplyToCollection() $queryBuilderProphecy->getEntityManager()->shouldBeCalled(2)->willReturn($emProphecy->reveal()); $queryBuilder = $queryBuilderProphecy->reveal(); - $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); - $orderExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class); + $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $eagerExtensionTest->applyToCollection($queryBuilder, new QueryNameGenerator(), Dummy::class); } public function testApplyToItem() { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $relatedNameCollection = new PropertyNameCollection(['id', 'name', 'notindatabase', 'notreadable', 'relation']); @@ -172,13 +183,16 @@ public function testApplyToItem() $queryBuilderProphecy->getEntityManager()->shouldBeCalled(2)->willReturn($emProphecy->reveal()); $queryBuilder = $queryBuilderProphecy->reveal(); - $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); + $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); $orderExtensionTest->applyToItem($queryBuilder, new QueryNameGenerator(), Dummy::class, []); } public function testCreateItemWithOperationName() { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); + $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); @@ -193,12 +207,15 @@ public function testCreateItemWithOperationName() $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); $queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy); - $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); + $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, [], 'item_operation'); } public function testCreateCollectionWithOperationName() { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); + $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); @@ -213,7 +230,156 @@ public function testCreateCollectionWithOperationName() $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); $queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy); - $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal()); - $orderExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, 'collection_operation'); + $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, 'collection_operation'); + } + + public function testDenormalizeItemWithCorrectResourceClass() + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + //Dummy is the correct class for the denormalization context serialization groups, and we're fetching RelatedDummy + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $classMetadataProphecy = $this->prophesize(ClassMetadata::class); + $classMetadataProphecy->associationMappings = []; + + $emProphecy = $this->prophesize(EntityManager::class); + $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); + $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); + $queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy); + + $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], 'item_operation', ['resource_class' => Dummy::class]); + } + + public function testDenormalizeItemWithExistingGroups() + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + //groups exist from the context, we don't need to compute them again + $resourceMetadataFactoryProphecy->create(Dummy::class)->shouldNotBeCalled(); + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $classMetadataProphecy = $this->prophesize(ClassMetadata::class); + $classMetadataProphecy->associationMappings = []; + + $emProphecy = $this->prophesize(EntityManager::class); + $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); + $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); + $queryBuilderProphecy->getEntityManager()->shouldBeCalled()->willReturn($emProphecy); + + $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), RelatedDummy::class, ['id' => 1], 'item_operation', ['groups' => 'some_groups']); + } + + /** + * @expectedException RuntimeException + * @expectedExceptionMessage The total number of joined relations has exceeded the specified maximum. Raise the limit if necessary. + */ + public function testMaxDepthReached() + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + + $relatedNameCollection = new PropertyNameCollection(['dummy']); + $dummyNameCollection = new PropertyNameCollection(['relatedDummy']); + + $propertyNameCollectionFactoryProphecy->create(RelatedDummy::class)->willReturn($relatedNameCollection)->shouldBeCalled(); + $propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn($dummyNameCollection)->shouldBeCalled(); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $relationPropertyMetadata = new PropertyMetadata(); + $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true); + + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relatedDummy', [])->willReturn($relationPropertyMetadata)->shouldBeCalled(); + + $relatedPropertyMetadata = new PropertyMetadata(); + $relatedPropertyMetadata = $relatedPropertyMetadata->withReadableLink(true); + + $propertyMetadataFactoryProphecy->create(RelatedDummy::class, 'dummy', [])->willReturn($relatedPropertyMetadata)->shouldBeCalled(); + + $classMetadataProphecy = $this->prophesize(ClassMetadata::class); + $classMetadataProphecy->associationMappings = [ + 'relatedDummy' => ['fetch' => 3, 'joinColumns' => [['nullable' => false]], 'targetEntity' => RelatedDummy::class], + ]; + + $relatedClassMetadataProphecy = $this->prophesize(ClassMetadata::class); + $relatedClassMetadataProphecy->associationMappings = [ + 'dummy' => ['fetch' => 3, 'joinColumns' => [['nullable' => false]], 'targetEntity' => Dummy::class], + ]; + + $emProphecy = $this->prophesize(EntityManager::class); + $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); + $emProphecy->getClassMetadata(RelatedDummy::class)->shouldBeCalled()->willReturn($relatedClassMetadataProphecy->reveal()); + + $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); + $queryBuilderProphecy->getEntityManager()->willReturn($emProphecy->reveal()); + + $queryBuilderProphecy->innerJoin(Argument::type('string'), Argument::type('string'))->shouldBeCalled(); + $queryBuilderProphecy->addSelect(Argument::type('string'))->shouldBeCalled(); + + $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal()); + $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class); + } + + public function testDisabled() + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + + $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); + $queryBuilderProphecy->getEntityManager()->shouldNotBeCalled(); + + $eagerExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), false); + $eagerExtensionTest->applyToCollection($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class); + $eagerExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, []); + } + + public function testFetchNotOnlyEager() + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(Dummy::class)->willReturn(new ResourceMetadata()); + + $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); + $propertyNameCollectionFactoryProphecy->create(UnknownDummy::class)->willReturn(new PropertyNameCollection(['id']))->shouldBeCalled(); + + $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); + $relationPropertyMetadata = new PropertyMetadata(); + $relationPropertyMetadata = $relationPropertyMetadata->withReadableLink(true); + + $idPropertyMetadata = new PropertyMetadata(); + $idPropertyMetadata = $idPropertyMetadata->withIdentifier(true); + + $propertyMetadataFactoryProphecy->create(UnknownDummy::class, 'id', [])->willReturn($idPropertyMetadata)->shouldBeCalled(); + $propertyMetadataFactoryProphecy->create(Dummy::class, 'relation', [])->willReturn($relationPropertyMetadata)->shouldBeCalled(); + + $queryBuilderProphecy = $this->prophesize(QueryBuilder::class); + + $classMetadataProphecy = $this->prophesize(ClassMetadata::class); + $classMetadataProphecy->associationMappings = [ + 'relation' => ['fetch' => 2, 'targetEntity' => UnknownDummy::class, 'joinColumns' => [['nullable' => false]]], + ]; + + $unknownClassMetadataProphecy = $this->prophesize(ClassMetadata::class); + $unknownClassMetadataProphecy->associationMappings = []; + + $emProphecy = $this->prophesize(EntityManager::class); + $emProphecy->getClassMetadata(Dummy::class)->shouldBeCalled()->willReturn($classMetadataProphecy->reveal()); + $emProphecy->getClassMetadata(UnknownDummy::class)->shouldBeCalled()->willReturn($unknownClassMetadataProphecy->reveal()); + + $queryBuilderProphecy->innerJoin('o.relation', 'a0')->shouldBeCalled(1); + $queryBuilderProphecy->addSelect('partial a0.{id}')->shouldBeCalled(1); + + $queryBuilderProphecy->getEntityManager()->shouldBeCalled(2)->willReturn($emProphecy->reveal()); + + $queryBuilder = $queryBuilderProphecy->reveal(); + + $orderExtensionTest = new EagerLoadingExtension($propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), $resourceMetadataFactoryProphecy->reveal(), true, 30, false); + $orderExtensionTest->applyToItem($queryBuilderProphecy->reveal(), new QueryNameGenerator(), Dummy::class, []); } } diff --git a/tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php b/tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php index 72e7ec6f971..f008f20de51 100644 --- a/tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php +++ b/tests/Bridge/Doctrine/Orm/ItemDataProviderTest.php @@ -38,6 +38,7 @@ class ItemDataProviderTest extends \PHPUnit_Framework_TestCase { public function testGetItemSingleIdentifier() { + $context = ['foo' => 'bar', 'fetch_data' => true]; $queryProphecy = $this->prophesize(AbstractQuery::class); $queryProphecy->getOneOrNullResult()->willReturn([])->shouldBeCalled(); @@ -68,7 +69,7 @@ public function testGetItemSingleIdentifier() $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo')->shouldBeCalled(); + $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo', $context)->shouldBeCalled(); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn(new PropertyNameCollection(['id']))->shouldBeCalled(); @@ -79,7 +80,7 @@ public function testGetItemSingleIdentifier() $dataProvider = new ItemDataProvider($managerRegistryProphecy->reveal(), $propertyNameCollectionFactoryProphecy->reveal(), $propertyMetadataFactoryProphecy->reveal(), [$extensionProphecy->reveal()]); - $this->assertEquals([], $dataProvider->getItem(Dummy::class, 1, 'foo')); + $this->assertEquals([], $dataProvider->getItem(Dummy::class, 1, 'foo', $context)); } public function testGetItemDoubleIdentifier() @@ -117,7 +118,7 @@ public function testGetItemDoubleIdentifier() $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); $extensionProphecy = $this->prophesize(QueryItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['ida' => 1, 'idb' => 2], 'foo')->shouldBeCalled(); + $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['ida' => 1, 'idb' => 2], 'foo', [])->shouldBeCalled(); $propertyNameCollectionFactoryProphecy = $this->prophesize(PropertyNameCollectionFactoryInterface::class); $propertyNameCollectionFactoryProphecy->create(Dummy::class)->willReturn(new PropertyNameCollection(['nonid', 'ida', 'idb']))->shouldBeCalled(); @@ -187,7 +188,7 @@ public function testQueryResultExtension() $managerRegistryProphecy->getManagerForClass(Dummy::class)->willReturn($managerProphecy->reveal())->shouldBeCalled(); $extensionProphecy = $this->prophesize(QueryResultItemExtensionInterface::class); - $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo')->shouldBeCalled(); + $extensionProphecy->applyToItem($queryBuilder, Argument::type(QueryNameGeneratorInterface::class), Dummy::class, ['id' => 1], 'foo', [])->shouldBeCalled(); $extensionProphecy->supportsResult(Dummy::class, 'foo')->willReturn(true)->shouldBeCalled(); $extensionProphecy->getResult($queryBuilder)->willReturn([])->shouldBeCalled(); diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php index 267c90eb03f..7a5d2613577 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ApiPlatformExtensionTest.php @@ -196,6 +196,9 @@ private function getContainerBuilderProphecy() 'api_platform.exception_to_status' => [ExceptionInterface::class => Response::HTTP_BAD_REQUEST, InvalidArgumentException::class => Response::HTTP_BAD_REQUEST], 'api_platform.title' => 'title', 'api_platform.version' => 'version', + 'api_platform.eager_loading.enabled' => true, + 'api_platform.eager_loading.max_joins' => 30, + 'api_platform.eager_loading.eager_only' => true, ]; foreach ($parameters as $key => $value) { $containerBuilderProphecy->setParameter($key, $value)->shouldBeCalled(); diff --git a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php index 8cb84bc5e06..4710091a958 100644 --- a/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php +++ b/tests/Bridge/Symfony/Bundle/DependencyInjection/ConfigurationTest.php @@ -70,6 +70,11 @@ public function testDefaultConfig() 'enable_fos_user' => false, 'enable_nelmio_api_doc' => false, 'enable_swagger' => true, + 'eager_loading' => [ + 'enabled' => true, + 'max_joins' => 30, + 'eager_only' => true, + ], 'collection' => [ 'order' => null, 'order_parameter_name' => 'order', diff --git a/tests/Bridge/Symfony/Routing/IriConverterTest.php b/tests/Bridge/Symfony/Routing/IriConverterTest.php index 786a5fd82e0..d38776ebb6a 100644 --- a/tests/Bridge/Symfony/Routing/IriConverterTest.php +++ b/tests/Bridge/Symfony/Routing/IriConverterTest.php @@ -90,7 +90,7 @@ public function testGetItemFromIriItemNotFoundException() $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy->getItem('AppBundle\Entity\User', 3, null, false)->shouldBeCalledTimes(1); + $itemDataProviderProphecy->getItem('AppBundle\Entity\User', 3, null, [])->shouldBeCalledTimes(1); $routeNameResolverProphecy = $this->prophesize(RouteNameResolverInterface::class); @@ -117,7 +117,7 @@ public function testGetItemFromIri() $propertyMetadataFactoryProphecy = $this->prophesize(PropertyMetadataFactoryInterface::class); $itemDataProviderProphecy = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProviderProphecy->getItem('AppBundle\Entity\User', 3, null, true) + $itemDataProviderProphecy->getItem('AppBundle\Entity\User', 3, null, ['fetch_data' => true]) ->willReturn('foo') ->shouldBeCalledTimes(1); @@ -136,6 +136,6 @@ public function testGetItemFromIri() $routeNameResolverProphecy->reveal(), $routerProphecy->reveal() ); - $converter->getItemFromIri('/users/3', true); + $converter->getItemFromIri('/users/3', ['fetch_data' => true]); } } diff --git a/tests/DataProvider/ChainItemDataProviderTest.php b/tests/DataProvider/ChainItemDataProviderTest.php index 790c6ea9d7c..18295b55b89 100644 --- a/tests/DataProvider/ChainItemDataProviderTest.php +++ b/tests/DataProvider/ChainItemDataProviderTest.php @@ -29,13 +29,13 @@ public function testGetItem() $dummy->setName('Lucie'); $firstDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $firstDataProvider->getItem(Dummy::class, 1, null, false)->willThrow(ResourceClassNotSupportedException::class); + $firstDataProvider->getItem(Dummy::class, 1, null, [])->willThrow(ResourceClassNotSupportedException::class); $secondDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $secondDataProvider->getItem(Dummy::class, 1, null, false)->willReturn($dummy); + $secondDataProvider->getItem(Dummy::class, 1, null, [])->willReturn($dummy); $thirdDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $thirdDataProvider->getItem(Dummy::class, 1, null, false)->willReturn(new \stdClass()); + $thirdDataProvider->getItem(Dummy::class, 1, null, [])->willReturn(new \stdClass()); $chainItemDataProvider = new ChainItemDataProvider([$firstDataProvider->reveal(), $secondDataProvider->reveal(), $thirdDataProvider->reveal()]); @@ -45,7 +45,7 @@ public function testGetItem() public function testGetItemExeptions() { $firstDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $firstDataProvider->getItem('notfound', 1, null, false)->willThrow(ResourceClassNotSupportedException::class); + $firstDataProvider->getItem('notfound', 1, null, [])->willThrow(ResourceClassNotSupportedException::class); $chainItemDataProvider = new ChainItemDataProvider([$firstDataProvider->reveal()]); diff --git a/tests/EventListener/ReadListenerTest.php b/tests/EventListener/ReadListenerTest.php index 8483c22a641..19140f06f2b 100644 --- a/tests/EventListener/ReadListenerTest.php +++ b/tests/EventListener/ReadListenerTest.php @@ -87,7 +87,7 @@ public function testRetrieveItem() $data = new \stdClass(); $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem('Foo', 1, 'get', true)->willReturn($data)->shouldBeCalled(); + $itemDataProvider->getItem('Foo', 1, 'get', ['fetch_data' => true])->willReturn($data)->shouldBeCalled(); $request = new Request([], [], ['id' => 1, '_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $request->setMethod(Request::METHOD_GET); @@ -109,7 +109,7 @@ public function testRetrieveItemNotFound() $collectionDataProvider = $this->prophesize(CollectionDataProviderInterface::class); $itemDataProvider = $this->prophesize(ItemDataProviderInterface::class); - $itemDataProvider->getItem('Foo', 22, 'get', true)->willReturn(null)->shouldBeCalled(); + $itemDataProvider->getItem('Foo', 22, 'get', ['fetch_data' => true])->willReturn(null)->shouldBeCalled(); $request = new Request([], [], ['id' => 22, '_api_resource_class' => 'Foo', '_api_item_operation_name' => 'get', '_api_format' => 'json', '_api_mime_type' => 'application/json']); $request->setMethod(Request::METHOD_GET); diff --git a/tests/Serializer/AbstractItemNormalizerTest.php b/tests/Serializer/AbstractItemNormalizerTest.php index cbf2bc5574e..2b214dc3ab9 100644 --- a/tests/Serializer/AbstractItemNormalizerTest.php +++ b/tests/Serializer/AbstractItemNormalizerTest.php @@ -247,8 +247,8 @@ public function testDenormalize() )->shouldBeCalled(); $iriConverterProphecy = $this->prophesize(IriConverterInterface::class); - $iriConverterProphecy->getItemFromIri('/dummies/1', true)->willReturn($relatedDummy1)->shouldBeCalled(); - $iriConverterProphecy->getItemFromIri('/dummies/2', true)->willReturn($relatedDummy2)->shouldBeCalled(); + $iriConverterProphecy->getItemFromIri('/dummies/1', Argument::type('array'))->willReturn($relatedDummy1)->shouldBeCalled(); + $iriConverterProphecy->getItemFromIri('/dummies/2', Argument::type('array'))->willReturn($relatedDummy2)->shouldBeCalled(); $propertyAccessorProphecy = $this->prophesize(PropertyAccessorInterface::class); $propertyAccessorProphecy->setValue(Argument::type(Dummy::class), 'name', 'foo')->shouldBeCalled();