diff --git a/src/Bridge/Doctrine/Orm/Util/QueryChecker.php b/src/Bridge/Doctrine/Orm/Util/QueryChecker.php index 303e4d83bc0..17b9beb5ca4 100644 --- a/src/Bridge/Doctrine/Orm/Util/QueryChecker.php +++ b/src/Bridge/Doctrine/Orm/Util/QueryChecker.php @@ -135,19 +135,41 @@ public static function hasOrderByOnToManyJoin(QueryBuilder $queryBuilder, Manage } } - if (!empty($orderByAliases)) { - foreach ($joinParts as $joins) { - foreach ($joins as $join) { - $alias = QueryJoinParser::getJoinAlias($join); + if (!$orderByAliases) { + return false; + } + + foreach ($joinParts as $joins) { + foreach ($joins as $join) { + $alias = QueryJoinParser::getJoinAlias($join); + + if (!isset($orderByAliases[$alias])) { + continue; + } - if (isset($orderByAliases[$alias])) { - $relationship = QueryJoinParser::getJoinRelationship($join); - list($parentAlias, $association) = explode('.', $relationship); + $relationship = QueryJoinParser::getJoinRelationship($join); - $parentMetadata = QueryJoinParser::getClassMetadataFromJoinAlias($parentAlias, $queryBuilder, $managerRegistry); + if (false !== strpos($relationship, '.')) { + $metadata = QueryJoinParser::getClassMetadataFromJoinAlias($alias, $queryBuilder, $managerRegistry); + if ($metadata->isCollectionValuedAssociation($relationship)) { + return true; + } + } else { + $parentMetadata = $managerRegistry->getManagerForClass($relationship)->getClassMetadata($relationship); + + foreach ($queryBuilder->getRootEntities() as $rootEntity) { + $rootMetadata = $managerRegistry + ->getManagerForClass($rootEntity) + ->getClassMetadata($rootEntity); + + if (!$rootMetadata instanceof ClassMetadata) { + continue; + } - if ($parentMetadata->isCollectionValuedAssociation($association)) { - return true; + foreach ($rootMetadata->getAssociationsByTargetClass($relationship) as $association => $mapping) { + if ($parentMetadata->isCollectionValuedAssociation($association)) { + return true; + } } } } diff --git a/src/Bridge/Doctrine/Orm/Util/QueryJoinParser.php b/src/Bridge/Doctrine/Orm/Util/QueryJoinParser.php index 42dfba2d45e..3ebdc5be839 100644 --- a/src/Bridge/Doctrine/Orm/Util/QueryJoinParser.php +++ b/src/Bridge/Doctrine/Orm/Util/QueryJoinParser.php @@ -59,10 +59,12 @@ public static function getClassMetadataFromJoinAlias(string $alias, QueryBuilder $pos = strpos($relationship, '.'); - $aliasMap[$alias] = [ - 'parentAlias' => substr($relationship, 0, $pos), - 'association' => substr($relationship, $pos + 1), - ]; + if (false !== $pos) { + $aliasMap[$alias] = [ + 'parentAlias' => substr($relationship, 0, $pos), + 'association' => substr($relationship, $pos + 1), + ]; + } } } diff --git a/tests/Bridge/Doctrine/Orm/Util/QueryCheckerTest.php b/tests/Bridge/Doctrine/Orm/Util/QueryCheckerTest.php index 65c96d34c6f..3b19c37b378 100644 --- a/tests/Bridge/Doctrine/Orm/Util/QueryCheckerTest.php +++ b/tests/Bridge/Doctrine/Orm/Util/QueryCheckerTest.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Util; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryChecker; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\Mapping\ClassMetadata; @@ -153,4 +154,25 @@ public function testHasOrderByOnToManyJoinWithoutJoinAndWithoutOrderBy() $this->assertFalse(QueryChecker::hasOrderByOnToManyJoin($queryBuilder->reveal(), $managerRegistry->reveal())); } + + public function testHasOrderByOnToManyJoinWithClassLeftJoin() + { + $queryBuilder = $this->prophesize(QueryBuilder::class); + $queryBuilder->getRootEntities()->willReturn(['Dummy']); + $queryBuilder->getRootAliases()->willReturn(['d']); + $queryBuilder->getDQLPart('join')->willReturn(['a_1' => [new Join('LEFT_JOIN', RelatedDummy::class, 'a_1', null, 'a_1.name = d.name')]]); + $queryBuilder->getDQLPart('orderBy')->willReturn(['a_1.name' => new OrderBy('a_1.name', 'asc')]); + $classMetadata = $this->prophesize(ClassMetadata::class); + $classMetadata->getAssociationsByTargetClass(RelatedDummy::class)->willReturn(['relatedDummy' => ['targetEntity' => RelatedDummy::class]]); + $relatedClassMetadata = $this->prophesize(ClassMetadata::class); + $relatedClassMetadata->isCollectionValuedAssociation('relatedDummy')->willReturn(true); + $objectManager = $this->prophesize(ObjectManager::class); + $objectManager->getClassMetadata('Dummy')->willReturn($classMetadata->reveal()); + $objectManager->getClassMetadata(RelatedDummy::class)->willReturn($relatedClassMetadata->reveal()); + $managerRegistry = $this->prophesize(ManagerRegistry::class); + $managerRegistry->getManagerForClass('Dummy')->willReturn($objectManager->reveal()); + $managerRegistry->getManagerForClass(RelatedDummy::class)->willReturn($objectManager->reveal()); + + $this->assertTrue(QueryChecker::hasOrderByOnToManyJoin($queryBuilder->reveal(), $managerRegistry->reveal())); + } } diff --git a/tests/Bridge/Doctrine/Orm/Util/QueryJoinParserTest.php b/tests/Bridge/Doctrine/Orm/Util/QueryJoinParserTest.php index eb4c7544921..8c5079cf4d5 100644 --- a/tests/Bridge/Doctrine/Orm/Util/QueryJoinParserTest.php +++ b/tests/Bridge/Doctrine/Orm/Util/QueryJoinParserTest.php @@ -14,6 +14,7 @@ namespace ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Util; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryJoinParser; +use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\RelatedDummy; use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\Common\Persistence\ObjectManager; use Doctrine\ORM\Mapping\ClassMetadata; @@ -43,16 +44,22 @@ public function testGetClassMetadataFromJoinAlias() public function testGetJoinRelationshipWithJoin() { - $join = new Join('INNER_JOIN', 'relatedDummy', 'a_1', null, 'a_1.name = r.name'); - $this->assertEquals('relatedDummy', QueryJoinParser::getJoinRelationship($join)); + $join = new Join('INNER_JOIN', 'a_1.relatedDummy', 'a_1', null, 'a_1.name = r.name'); + $this->assertEquals('a_1.relatedDummy', QueryJoinParser::getJoinRelationship($join)); + } + + public function testGetJoinRelationshipWithClassJoin() + { + $join = new Join('INNER_JOIN', RelatedDummy::class, 'a_1', null, 'a_1.name = r.name'); + $this->assertEquals(RelatedDummy::class, QueryJoinParser::getJoinRelationship($join)); } public function testGetJoinRelationshipWithReflection() { $methodExist = $this->getFunctionMock('ApiPlatform\Core\Bridge\Doctrine\Orm\Util', 'method_exists'); $methodExist->expects($this->any())->with(Join::class, 'getJoin')->willReturn('false'); - $join = new Join('INNER_JOIN', 'relatedDummy', 'a_1', null, 'a_1.name = r.name'); - $this->assertEquals('relatedDummy', QueryJoinParser::getJoinRelationship($join)); + $join = new Join('INNER_JOIN', 'a_1.relatedDummy', 'a_1', null, 'a_1.name = r.name'); + $this->assertEquals('a_1.relatedDummy', QueryJoinParser::getJoinRelationship($join)); } public function testGetJoinAliasWithJoin() @@ -61,6 +68,12 @@ public function testGetJoinAliasWithJoin() $this->assertEquals('a_1', QueryJoinParser::getJoinAlias($join)); } + public function testGetJoinAliasWithClassJoin() + { + $join = new Join('LEFT_JOIN', RelatedDummy::class, 'a_1', null, 'a_1.name = r.name'); + $this->assertEquals('a_1', QueryJoinParser::getJoinAlias($join)); + } + public function testGetJoinAliasWithReflection() { $methodExist = $this->getFunctionMock('ApiPlatform\Core\Bridge\Doctrine\Orm\Util', 'method_exists');