From e114c46b5ec2dcf7d41294e24a43a1d766460efe Mon Sep 17 00:00:00 2001 From: abluchet Date: Thu, 18 May 2017 10:03:06 +0200 Subject: [PATCH] specify left join for filter associations --- .../Doctrine/Orm/Filter/AbstractFilter.php | 11 +++++++-- src/Bridge/Doctrine/Orm/Util/QueryChecker.php | 21 +++++++++++++++++ .../Doctrine/Orm/Filter/SearchFilterTest.php | 23 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php b/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php index 4ad94b6c4d4..2d1eec718d2 100644 --- a/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php +++ b/src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php @@ -13,6 +13,7 @@ namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Filter; +use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryChecker; use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface; use ApiPlatform\Core\Exception\InvalidArgumentException; use ApiPlatform\Core\Util\RequestParser; @@ -287,8 +288,14 @@ protected function addJoinOnce(QueryBuilder $queryBuilder, QueryNameGeneratorInt if (null === $join) { $associationAlias = $queryNameGenerator->generateJoinAlias($association); - $queryBuilder - ->join(sprintf('%s.%s', $alias, $association), $associationAlias); + + if (true === QueryChecker::hasLeftJoin($queryBuilder)) { + $queryBuilder + ->leftJoin(sprintf('%s.%s', $alias, $association), $associationAlias); + } else { + $queryBuilder + ->innerJoin(sprintf('%s.%s', $alias, $association), $associationAlias); + } } else { $associationAlias = $join->getAlias(); } diff --git a/src/Bridge/Doctrine/Orm/Util/QueryChecker.php b/src/Bridge/Doctrine/Orm/Util/QueryChecker.php index f07121a57ea..303e4d83bc0 100644 --- a/src/Bridge/Doctrine/Orm/Util/QueryChecker.php +++ b/src/Bridge/Doctrine/Orm/Util/QueryChecker.php @@ -15,6 +15,7 @@ use Doctrine\Common\Persistence\ManagerRegistry; use Doctrine\ORM\Mapping\ClassMetadata; +use Doctrine\ORM\Query\Expr\Join; use Doctrine\ORM\QueryBuilder; /** @@ -155,4 +156,24 @@ public static function hasOrderByOnToManyJoin(QueryBuilder $queryBuilder, Manage return false; } + + /** + * Determines whether the query builder already has a left join. + * + * @param QueryBuilder $queryBuilder + * + * @return bool + */ + public static function hasLeftJoin(QueryBuilder $queryBuilder): bool + { + foreach ($queryBuilder->getDQLPart('join') as $dqlParts) { + foreach ($dqlParts as $dqlPart) { + if (Join::LEFT_JOIN === $dqlPart->getJoinType()) { + return true; + } + } + } + + return false; + } } diff --git a/tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php b/tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php index 5f49a550f77..087bb18ae7c 100644 --- a/tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php +++ b/tests/Bridge/Doctrine/Orm/Filter/SearchFilterTest.php @@ -767,4 +767,27 @@ public function testTripleJoin() $expected = strtolower(sprintf('SELECT o FROM %s o inner join o.relatedDummy relateddummy_a1 inner join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', Dummy::class)); $this->assertEquals($actual, $expected); } + + public function testJoinLeft() + { + $request = Request::create('/api/dummies', 'GET', ['relatedDummy.symfony' => 'foo', 'relatedDummy.thirdLevel.level' => 'bar']); + $requestStack = new RequestStack(); + $requestStack->push($request); + $queryBuilder = $this->repository->createQueryBuilder('o'); + $queryBuilder->leftJoin('o.relatedDummy', 'relateddummy_a1'); + + $filter = new SearchFilter( + $this->managerRegistry, + $requestStack, + $this->iriConverter, + $this->propertyAccessor, + null, + ['relatedDummy.symfony' => null, 'relatedDummy.thirdLevel.level' => null] + ); + + $filter->apply($queryBuilder, new QueryNameGenerator(), $this->resourceClass, 'op'); + $actual = strtolower($queryBuilder->getQuery()->getDQL()); + $expected = strtolower(sprintf('SELECT o FROM %s o left join o.relatedDummy relateddummy_a1 left join relateddummy_a1.thirdLevel thirdLevel_a1 WHERE relateddummy_a1.symfony = :symfony_p1 and thirdLevel_a1.level = :level_p2', Dummy::class)); + $this->assertEquals($actual, $expected); + } }