Skip to content

Commit 2fe67f5

Browse files
authored
Merge pull request #2145 from soyuka/fix-2109
OrderFilter with nullable relation uses LEFT JOIN fix #2109
2 parents 3292640 + 34bb2ff commit 2fe67f5

File tree

4 files changed

+36
-4
lines changed

4 files changed

+36
-4
lines changed

src/Bridge/Doctrine/Orm/Extension/FilterExtension.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
use ApiPlatform\Core\Api\FilterCollection;
1717
use ApiPlatform\Core\Api\FilterLocatorTrait;
1818
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\FilterInterface;
19+
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
1920
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
2021
use ApiPlatform\Core\Exception\InvalidArgumentException;
2122
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
@@ -60,12 +61,25 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
6061
return;
6162
}
6263

64+
$orderFilter = null;
65+
6366
foreach ($resourceFilters as $filterId) {
6467
$filter = $this->getFilter($filterId);
6568
if ($filter instanceof FilterInterface) {
69+
// Apply the OrderFilter after every other filter to avoid an edge case where OrderFilter would do a LEFT JOIN instead of an INNER JOIN
70+
if ($filter instanceof OrderFilter) {
71+
$orderFilter = $filter;
72+
continue;
73+
}
74+
6675
$context['filters'] = $context['filters'] ?? [];
6776
$filter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
6877
}
6978
}
79+
80+
if (null !== $orderFilter) {
81+
$context['filters'] = $context['filters'] ?? [];
82+
$orderFilter->apply($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context);
83+
}
7084
}
7185
}

src/Bridge/Doctrine/Orm/Filter/AbstractFilter.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ protected function extractProperties(Request $request/*, string $resourceClass*/
269269
* the second element is the $field name
270270
* the third element is the $associations array
271271
*/
272-
protected function addJoinsForNestedProperty(string $property, string $rootAlias, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator/*, string $resourceClass*/): array
272+
protected function addJoinsForNestedProperty(string $property, string $rootAlias, QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator/*, string $resourceClass, string $joinType*/): array
273273
{
274274
if (\func_num_args() > 4) {
275275
$resourceClass = func_get_arg(4);
@@ -283,12 +283,18 @@ protected function addJoinsForNestedProperty(string $property, string $rootAlias
283283
$resourceClass = null;
284284
}
285285

286+
if (\func_num_args() > 5) {
287+
$joinType = func_get_arg(5);
288+
} else {
289+
$joinType = null;
290+
}
291+
286292
$propertyParts = $this->splitPropertyParts($property, $resourceClass);
287293
$parentAlias = $rootAlias;
288294
$alias = null;
289295

290296
foreach ($propertyParts['associations'] as $association) {
291-
$alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $parentAlias, $association);
297+
$alias = QueryBuilderHelper::addJoinOnce($queryBuilder, $queryNameGenerator, $parentAlias, $association, $joinType);
292298
$parentAlias = $alias;
293299
}
294300

src/Bridge/Doctrine/Orm/Filter/OrderFilter.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
1717
use Doctrine\Common\Persistence\ManagerRegistry;
18+
use Doctrine\ORM\Query\Expr\Join;
1819
use Doctrine\ORM\QueryBuilder;
1920
use Psr\Log\LoggerInterface;
2021
use Symfony\Component\HttpFoundation\Request;
@@ -146,7 +147,7 @@ protected function filterProperty(string $property, $direction, QueryBuilder $qu
146147
$field = $property;
147148

148149
if ($this->isPropertyNested($property, $resourceClass)) {
149-
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass);
150+
list($alias, $field) = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::LEFT_JOIN);
150151
}
151152

152153
if (null !== $nullsComparison = $this->properties[$property]['nulls_comparison'] ?? null) {

tests/Bridge/Doctrine/Orm/Filter/OrderFilterTest.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ public function provideApplyTestData(): array
255255
'relatedDummy.symfony' => 'desc',
256256
],
257257
],
258-
sprintf('SELECT o FROM %s o INNER JOIN o.relatedDummy relatedDummy_a1 ORDER BY o.id ASC, o.name DESC, relatedDummy_a1.symfony DESC', Dummy::class),
258+
sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY o.id ASC, o.name DESC, relatedDummy_a1.symfony DESC', Dummy::class),
259259
null,
260260
$orderFilterFactory,
261261
],
@@ -354,6 +354,17 @@ public function provideApplyTestData(): array
354354
null,
355355
$orderFilterFactory,
356356
],
357+
'not nullable relation will be a LEFT JOIN' => [
358+
[
359+
'relatedDummy.name' => 'ASC',
360+
],
361+
[
362+
'order' => ['relatedDummy.name' => 'ASC'],
363+
],
364+
sprintf('SELECT o FROM %s o LEFT JOIN o.relatedDummy relatedDummy_a1 ORDER BY relatedDummy_a1.name ASC', Dummy::class),
365+
null,
366+
$orderFilterFactory,
367+
],
357368
];
358369
}
359370
}

0 commit comments

Comments
 (0)