diff --git a/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php b/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php index 486ee2d9a86..4dcae6898ba 100644 --- a/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php +++ b/src/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtension.php @@ -48,14 +48,31 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator } $joinParts = $queryBuilder->getDQLPart('join'); + $originAlias = 'o'; - if (!$joinParts || !isset($joinParts['o'])) { + if (!$joinParts || !isset($joinParts[$originAlias])) { return; } $queryBuilderClone = clone $queryBuilder; $queryBuilderClone->resetDQLPart('where'); - $queryBuilderClone->andWhere($queryBuilderClone->expr()->in('o', $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator)->getDQL())); + + $classMetadata = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass); + + if (!$classMetadata->isIdentifierComposite) { + $replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias); + $in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias); + $in->select($replacementAlias); + $queryBuilderClone->andWhere($queryBuilderClone->expr()->in($originAlias, $in->getDQL())); + } else { + // Because Doctrine doesn't support WHERE ( foo, bar ) IN () (https://github.com/doctrine/doctrine2/issues/5238), we are building as many subqueries as they are identifiers + foreach ($classMetadata->identifier as $identifier) { + $replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias); + $in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias); + $in->select("IDENTITY($replacementAlias.$identifier)"); + $queryBuilderClone->andWhere($queryBuilderClone->expr()->in("$originAlias.$identifier", $in->getDQL())); + } + } $queryBuilder->resetDQLPart('where'); $queryBuilder->add('where', $queryBuilderClone->getDQLPart('where')); @@ -72,7 +89,6 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $originAlias = 'o', string $replacement = 'o_2') { $queryBuilderClone = clone $queryBuilder; - $queryBuilderClone->select($replacement); $joinParts = $queryBuilder->getDQLPart('join'); $wherePart = $queryBuilder->getDQLPart('where'); @@ -80,6 +96,9 @@ private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, Query //reset parts $queryBuilderClone->resetDQLPart('join'); $queryBuilderClone->resetDQLPart('where'); + $queryBuilderClone->resetDQLPart('orderBy'); + $queryBuilderClone->resetDQLPart('groupBy'); + $queryBuilderClone->resetDQLPart('having'); //Change from alias $from = $queryBuilderClone->getDQLPart('from')[0]; diff --git a/tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php b/tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php index 78dbdec3212..b1a3a7612fd 100644 --- a/tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php +++ b/tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php @@ -15,9 +15,11 @@ 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\CompositeRelation; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar; use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Foo; use Doctrine\ORM\EntityManager; +use Doctrine\ORM\Mapping\ClassMetadataInfo; use Doctrine\ORM\Query\Expr; use Doctrine\ORM\QueryBuilder; @@ -112,6 +114,7 @@ public function testApplyCollection() $em = $this->prophesize(EntityManager::class); $em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr()); + $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); $qb = new QueryBuilder($em->reveal()); @@ -123,10 +126,150 @@ public function testApplyCollection() $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); $queryNameGenerator->generateJoinAlias('colors')->shouldBeCalled()->willReturn('colors_2'); + $queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2'); $filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), true); $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get'); $this->assertEquals('SELECT o FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o LEFT JOIN o.colors colors WHERE o IN(SELECT o_2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o_2 LEFT JOIN o_2.colors colors_2 WHERE o_2.colors = :foo)', $qb->getDQL()); } + + /** + * https://github.com/api-platform/core/issues/1021. + */ + public function testHiddenOrderBy() + { + $resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class); + $resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class)); + + $em = $this->prophesize(EntityManager::class); + $em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr()); + $em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class)); + + $qb = new QueryBuilder($em->reveal()); + + $qb->select('o', 'CASE WHEN o.dateCreated IS NULL THEN 0 ELSE 1 END AS HIDDEN _o_dateCreated_null_rank') + ->from(DummyCar::class, 'o') + ->leftJoin('o.colors', 'colors') + ->where('o.colors = :foo') + ->orderBy('_o_dateCreated_null_rank DESC') + ->setParameter('foo', 1); + + $queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class); + $queryNameGenerator->generateJoinAlias('colors')->shouldBeCalled()->willReturn('colors_2'); + $queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2'); + $filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), true); + $filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get'); + + $expected = <<