diff --git a/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php b/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php index 4657980d05e..17fb84f9906 100644 --- a/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php +++ b/src/Bridge/Doctrine/Orm/Extension/EagerLoadingExtension.php @@ -59,13 +59,9 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $forceEager = $this->isForceEager($resourceClass, $options); - try { - $groups = $this->getSerializerGroups($resourceClass, $options, 'normalization_context'); + $groups = $this->getSerializerGroups($resourceClass, $options, 'normalization_context'); - $this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $queryBuilder->getRootAliases()[0], $groups); - } catch (ResourceClassNotFoundException $resourceClassNotFoundException) { - //ignore the not found exception - } + $this->joinRelations($queryBuilder, $queryNameGenerator, $resourceClass, $forceEager, $queryBuilder->getRootAliases()[0], $groups); } /** diff --git a/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php b/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php index 4dcae6898ba..965177e264d 100644 --- a/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php +++ b/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php @@ -13,6 +13,8 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; +use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; @@ -36,7 +38,10 @@ public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFa */ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $resourceClass, string $operationName = null) { - if (false === $this->forceEager || false === $this->isForceEager($resourceClass, ['collection_operation_name' => $operationName])) { + $em = $queryBuilder->getEntityManager(); + $classMetadata = $em->getClassMetadata($resourceClass); + + if (!$this->hasFetchEagerAssociation($em, $classMetadata) && (false === $this->forceEager || false === $this->isForceEager($resourceClass, ['collection_operation_name' => $operationName]))) { return; } @@ -57,8 +62,6 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator $queryBuilderClone = clone $queryBuilder; $queryBuilderClone->resetDQLPart('where'); - $classMetadata = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass); - if (!$classMetadata->isIdentifierComposite) { $replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias); $in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias); @@ -143,4 +146,27 @@ private function isForceEager(string $resourceClass, array $options): bool return is_bool($forceEager) ? $forceEager : $this->forceEager; } + + private function hasFetchEagerAssociation(EntityManager $em, ClassMetadataInfo $classMetadata, &$checked = []) + { + $checked[] = $classMetadata->name; + + foreach ($classMetadata->associationMappings as $mapping) { + if (ClassMetadataInfo::FETCH_EAGER === $mapping['fetch']) { + return true; + } + + $related = $em->getClassMetadata($mapping['targetEntity']); + + if (in_array($related->name, $checked)) { + continue; + } + + if (true === $this->hasFetchEagerAssociation($em, $related, $checked)) { + return true; + } + } + + return false; + } } diff --git a/tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php b/tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php index b1a3a7612fd..8cde181bd58 100644 --- a/tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php +++ b/tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php @@ -15,6 +15,8 @@ use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; use ApiPlatform\Core\Metadata\Resource\ResourceMetadata; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeItem; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeLabel; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Foo; @@ -37,8 +39,12 @@ public function testIsNoForceEagerCollectionAttributes() ], ], null)); + $em = $this->prophesize(EntityManager::class); + $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); + $qb = $this->prophesize(QueryBuilder::class); $qb->getDQLPart('where')->shouldNotBeCalled(); + $qb->getEntityManager()->willReturn($em); $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); @@ -48,6 +54,9 @@ public function testIsNoForceEagerCollectionAttributes() public function testIsNoForceEagerResource() { + $em = $this->prophesize(EntityManager::class); + $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class, null, null, null, [ 'get' => [], @@ -55,6 +64,7 @@ public function testIsNoForceEagerResource() $qb = $this->prophesize(QueryBuilder::class); $qb->getDQLPart('where')->shouldNotBeCalled(); + $qb->getEntityManager()->willReturn($em); $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); @@ -64,6 +74,9 @@ public function testIsNoForceEagerResource() public function testIsForceEagerConfig() { + $em = $this->prophesize(EntityManager::class); + $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class, null, null, null, [ 'get' => [], @@ -71,6 +84,7 @@ public function testIsForceEagerConfig() $qb = $this->prophesize(QueryBuilder::class); $qb->getDQLPart('where')->shouldNotBeCalled(); + $qb->getEntityManager()->willReturn($em); $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); @@ -80,11 +94,15 @@ public function testIsForceEagerConfig() public function testHasNoWherePart() { + $em = $this->prophesize(EntityManager::class); + $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class)); $qb = $this->prophesize(QueryBuilder::class); $qb->getDQLPart('where')->shouldBeCalled()->willReturn(null); + $qb->getEntityManager()->willReturn($em); $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); @@ -94,12 +112,16 @@ public function testHasNoWherePart() public function testHasNoJoinPart() { + $em = $this->prophesize(EntityManager::class); + $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class)); $qb = $this->prophesize(QueryBuilder::class); $qb->getDQLPart('where')->shouldBeCalled()->willReturn(new Expr\Andx()); $qb->getDQLPart('join')->shouldBeCalled()->willReturn(null); + $qb->getEntityManager()->willReturn($em); $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); @@ -162,12 +184,12 @@ public function testHiddenOrderBy() $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get'); $expected = <<