Skip to content

Commit 7a59035

Browse files
abluchetsoyuka
authored andcommitted
Fix #1023 allow filter eager loading on composite identifiers
1 parent 53de77b commit 7a59035

File tree

2 files changed

+78
-3
lines changed

2 files changed

+78
-3
lines changed

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,31 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
4848
}
4949

5050
$joinParts = $queryBuilder->getDQLPart('join');
51+
$originAlias = 'o';
5152

52-
if (!$joinParts || !isset($joinParts['o'])) {
53+
if (!$joinParts || !isset($joinParts[$originAlias])) {
5354
return;
5455
}
5556

5657
$queryBuilderClone = clone $queryBuilder;
5758
$queryBuilderClone->resetDQLPart('where');
58-
$queryBuilderClone->andWhere($queryBuilderClone->expr()->in('o', $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator)->getDQL()));
59+
60+
$classMetadata = $queryBuilder->getEntityManager()->getClassMetadata($resourceClass);
61+
62+
if (!$classMetadata->isIdentifierComposite) {
63+
$replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias);
64+
$in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias);
65+
$in->select($replacementAlias);
66+
$queryBuilderClone->andWhere($queryBuilderClone->expr()->in($originAlias, $in->getDQL()));
67+
} else {
68+
// 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
69+
foreach ($classMetadata->identifier as $identifier) {
70+
$replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias);
71+
$in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias);
72+
$in->select("IDENTITY($replacementAlias.$identifier)");
73+
$queryBuilderClone->andWhere($queryBuilderClone->expr()->in("$originAlias.$identifier", $in->getDQL()));
74+
}
75+
}
5976

6077
$queryBuilder->resetDQLPart('where');
6178
$queryBuilder->add('where', $queryBuilderClone->getDQLPart('where'));
@@ -72,7 +89,6 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
7289
private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $originAlias = 'o', string $replacement = 'o_2')
7390
{
7491
$queryBuilderClone = clone $queryBuilder;
75-
$queryBuilderClone->select($replacement);
7692

7793
$joinParts = $queryBuilder->getDQLPart('join');
7894
$wherePart = $queryBuilder->getDQLPart('where');

tests/Bridge/Doctrine/Orm/Extension/FilterEagerLoadingExtensionTest.php

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
1616
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
1717
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
18+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation;
1819
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar;
1920
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\Foo;
2021
use Doctrine\ORM\EntityManager;
22+
use Doctrine\ORM\Mapping\ClassMetadataInfo;
2123
use Doctrine\ORM\Query\Expr;
2224
use Doctrine\ORM\QueryBuilder;
2325

@@ -112,6 +114,7 @@ public function testApplyCollection()
112114

113115
$em = $this->prophesize(EntityManager::class);
114116
$em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr());
117+
$em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class));
115118

116119
$qb = new QueryBuilder($em->reveal());
117120

@@ -123,6 +126,7 @@ public function testApplyCollection()
123126

124127
$queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class);
125128
$queryNameGenerator->generateJoinAlias('colors')->shouldBeCalled()->willReturn('colors_2');
129+
$queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2');
126130

127131
$filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), true);
128132
$filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get');
@@ -140,6 +144,7 @@ public function testHiddenOrderBy()
140144

141145
$em = $this->prophesize(EntityManager::class);
142146
$em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr());
147+
$em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class));
143148

144149
$qb = new QueryBuilder($em->reveal());
145150

@@ -152,6 +157,7 @@ public function testHiddenOrderBy()
152157

153158
$queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class);
154159
$queryNameGenerator->generateJoinAlias('colors')->shouldBeCalled()->willReturn('colors_2');
160+
$queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2');
155161
$filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), true);
156162
$filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get');
157163

@@ -176,6 +182,7 @@ public function testGroupBy()
176182

177183
$em = $this->prophesize(EntityManager::class);
178184
$em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr());
185+
$em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class));
179186

180187
$qb = new QueryBuilder($em->reveal());
181188

@@ -190,6 +197,7 @@ public function testGroupBy()
190197

191198
$queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class);
192199
$queryNameGenerator->generateJoinAlias('colors')->shouldBeCalled()->willReturn('colors_2');
200+
$queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2');
193201
$filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), true);
194202
$filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get');
195203

@@ -209,6 +217,57 @@ public function testGroupBy()
209217
$this->assertEquals($this->toDQLString($expected), $qb->getDQL());
210218
}
211219

220+
public function testCompositeIdentifiers()
221+
{
222+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
223+
$resourceMetadataFactoryProphecy->create(CompositeRelation::class)->willReturn(new ResourceMetadata(CompositeRelation::class));
224+
225+
$classMetadata = new ClassMetadataInfo(CompositeRelation::class);
226+
$classMetadata->isIdentifierComposite = true;
227+
$classMetadata->identifier = ['item', 'label'];
228+
229+
$em = $this->prophesize(EntityManager::class);
230+
$em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr());
231+
$em->getClassMetadata(CompositeRelation::class)->shouldBeCalled()->willReturn($classMetadata);
232+
233+
$qb = new QueryBuilder($em->reveal());
234+
235+
$qb->select('o')
236+
->from(CompositeRelation::class, 'o')
237+
->innerJoin('o.compositeItem', 'item')
238+
->innerJoin('o.compositeLabel', 'label')
239+
->where('item.field1 = :foo')
240+
->setParameter('foo', 1);
241+
242+
$queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class);
243+
$queryNameGenerator->generateJoinAlias('item')->shouldBeCalled()->willReturn('item_2');
244+
$queryNameGenerator->generateJoinAlias('label')->shouldBeCalled()->willReturn('label_2');
245+
$queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2');
246+
247+
$filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), true);
248+
$filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), CompositeRelation::class, 'get');
249+
250+
$expected = <<<SQL
251+
SELECT o
252+
FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o
253+
INNER JOIN o.compositeItem item
254+
INNER JOIN o.compositeLabel label
255+
WHERE o.item IN(
256+
SELECT IDENTITY(o_2.item) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o_2
257+
INNER JOIN o_2.compositeItem item_2
258+
INNER JOIN o_2.compositeLabel label_2
259+
WHERE item_2.field1 = :foo
260+
) AND o.label IN(
261+
SELECT IDENTITY(o_2.label) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation o_2
262+
INNER JOIN o_2.compositeItem item_2
263+
INNER JOIN o_2.compositeLabel label_2
264+
WHERE item_2.field1 = :foo
265+
)
266+
SQL;
267+
268+
$this->assertEquals($this->toDQLString($expected), $qb->getDQL());
269+
}
270+
212271
private function toDQLString(string $dql): string
213272
{
214273
return preg_replace('/\\r\\n|\\n/', '', str_replace(' ', '', $dql));

0 commit comments

Comments
 (0)