Skip to content

Commit e5559b4

Browse files
authored
Merge pull request #2622 from antograssiot/preserve-join-filtereagerloading
Preserve manual join in FilterEagerLoadingExtension
2 parents 455030a + 06cd22b commit e5559b4

File tree

5 files changed

+163
-1
lines changed

5 files changed

+163
-1
lines changed

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
namespace ApiPlatform\Core\Bridge\Doctrine\Orm\Extension;
1515

16+
use ApiPlatform\Core\Api\ResourceClassResolver;
1617
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\EagerLoadingTrait;
1718
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryBuilderHelper;
1819
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
@@ -29,10 +30,13 @@ final class FilterEagerLoadingExtension implements ContextAwareQueryCollectionEx
2930
{
3031
use EagerLoadingTrait;
3132

32-
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, bool $forceEager = true)
33+
private $resourceClassResolver;
34+
35+
public function __construct(ResourceMetadataFactoryInterface $resourceMetadataFactory, bool $forceEager = true, ResourceClassResolver $resourceClassResolver = null)
3336
{
3437
$this->resourceMetadataFactory = $resourceMetadataFactory;
3538
$this->forceEager = $forceEager;
39+
$this->resourceClassResolver = $resourceClassResolver;
3640
}
3741

3842
/**
@@ -134,6 +138,15 @@ private function getQueryBuilderWithNewAliases(QueryBuilder $queryBuilder, Query
134138
$joinString = str_replace($aliases, $replacements, $joinPart->getJoin());
135139
$pos = strpos($joinString, '.');
136140
if (false === $pos) {
141+
if (null !== $joinPart->getCondition() && null !== $this->resourceClassResolver && $this->resourceClassResolver->isResourceClass($joinString)) {
142+
$newAlias = $queryNameGenerator->generateJoinAlias($joinPart->getAlias());
143+
$aliases[] = "{$joinPart->getAlias()}.";
144+
$replacements[] = "$newAlias.";
145+
$condition = str_replace($aliases, $replacements, $joinPart->getCondition());
146+
$join = new Join($joinPart->getJoinType(), $joinPart->getJoin(), $newAlias, $joinPart->getConditionType(), $condition);
147+
$queryBuilderClone->add('join', [$replacement => $join], true);
148+
}
149+
137150
continue;
138151
}
139152
$alias = substr($joinString, 0, $pos);

src/Bridge/Symfony/Bundle/Resources/config/doctrine_orm.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
<service id="api_platform.doctrine.orm.query_extension.filter_eager_loading" class="ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterEagerLoadingExtension" public="false">
140140
<argument type="service" id="api_platform.metadata.resource.metadata_factory" />
141141
<argument>%api_platform.eager_loading.force_eager%</argument>
142+
<argument type="service" id="api_platform.resource_class_resolver" />
142143

143144
<tag name="api_platform.doctrine.orm.query_extension.collection" priority="-17" />
144145
</service>

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

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@
1313

1414
namespace ApiPlatform\Core\Tests\Bridge\Doctrine\Orm\Extension;
1515

16+
use ApiPlatform\Core\Api\ResourceClassResolver;
1617
use ApiPlatform\Core\Bridge\Doctrine\Orm\Extension\FilterEagerLoadingExtension;
1718
use ApiPlatform\Core\Bridge\Doctrine\Orm\Util\QueryNameGeneratorInterface;
1819
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface;
20+
use ApiPlatform\Core\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
1921
use ApiPlatform\Core\Metadata\Resource\ResourceMetadata;
22+
use ApiPlatform\Core\Metadata\Resource\ResourceNameCollection;
2023
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeItem;
2124
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeLabel;
2225
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\CompositeRelation;
2326
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar;
27+
use ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyTravel;
2428
use Doctrine\ORM\EntityManager;
2529
use Doctrine\ORM\Mapping\ClassMetadataInfo;
2630
use Doctrine\ORM\Query\Expr;
@@ -159,6 +163,55 @@ public function testApplyCollection()
159163
$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());
160164
}
161165

166+
public function testApplyCollectionWithManualJoin()
167+
{
168+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
169+
$resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class));
170+
171+
$resourceNameCollectionFactoryProphecy = $this->prophesize(ResourceNameCollectionFactoryInterface::class);
172+
$resourceNameCollectionFactoryProphecy->create()->willReturn(new ResourceNameCollection([DummyTravel::class]));
173+
174+
$em = $this->prophesize(EntityManager::class);
175+
$em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr());
176+
$em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn(new ClassMetadataInfo(DummyCar::class));
177+
178+
$qb = new QueryBuilder($em->reveal());
179+
180+
$qb->select('o')
181+
->from(DummyCar::class, 'o')
182+
->leftJoin('o.colors', 'colors')
183+
->join(DummyTravel::class, 't_a3', Expr\Join::WITH, 'o.id = t_a3.car AND t_a3.passenger = :user')
184+
->where('o.colors = :foo')
185+
->andwhere('t_a3.confirmed = :confirmation')
186+
->setParameter('foo', 1)
187+
->setParameter('user', 2)
188+
->setParameter('confirmation', true)
189+
;
190+
191+
$queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class);
192+
$queryNameGenerator->generateJoinAlias('colors')->shouldBeCalled()->willReturn('colors_2');
193+
$queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2');
194+
$queryNameGenerator->generateJoinAlias('t_a3')->shouldBeCalled()->willReturn('t_a3_a20');
195+
196+
$filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), true, new ResourceClassResolver($resourceNameCollectionFactoryProphecy->reveal()));
197+
$filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get');
198+
199+
$expected = <<<'SQL'
200+
SELECT o
201+
FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o
202+
LEFT JOIN o.colors colors
203+
INNER JOIN ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyTravel t_a3 WITH o.id = t_a3.car AND t_a3.passenger = :user
204+
WHERE o IN(
205+
SELECT o_2 FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o_2
206+
LEFT JOIN o_2.colors colors_2
207+
INNER JOIN ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyTravel t_a3_a20 WITH o_2.id = t_a3_a20.car AND t_a3_a20.passenger = :user
208+
WHERE o_2.colors = :foo AND t_a3_a20.confirmed = :confirmation
209+
)
210+
SQL;
211+
212+
$this->assertEquals($this->toDQLString($expected), $qb->getDQL());
213+
}
214+
162215
/**
163216
* https://github.com/api-platform/core/issues/1021.
164217
*/
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use Doctrine\ORM\Mapping as ORM;
18+
19+
/**
20+
* @ApiResource
21+
* @ORM\Entity
22+
*/
23+
class DummyPassenger
24+
{
25+
/**
26+
* @var int The entity Id
27+
*
28+
* @ORM\Id
29+
* @ORM\GeneratedValue
30+
* @ORM\Column(type="integer")
31+
*/
32+
private $id;
33+
34+
/**
35+
* @ORM\Column(type="string")
36+
*/
37+
public $nickname;
38+
39+
public function getId()
40+
{
41+
return $this->id;
42+
}
43+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity;
15+
16+
use ApiPlatform\Core\Annotation\ApiResource;
17+
use Doctrine\ORM\Mapping as ORM;
18+
19+
/**
20+
* @ApiResource
21+
* @ORM\Entity
22+
*/
23+
class DummyTravel
24+
{
25+
/**
26+
* @ORM\Id
27+
* @ORM\GeneratedValue
28+
* @ORM\Column(type="integer")
29+
*/
30+
private $id;
31+
32+
/**
33+
* @ORM\ManyToOne(targetEntity="DummyCar")
34+
* @ORM\JoinColumn(name="car_id", referencedColumnName="id")
35+
*/
36+
public $car;
37+
38+
/**
39+
* @ORM\Column(type="boolean")
40+
*/
41+
public $confirmed;
42+
/**
43+
* @ORM\ManyToOne(targetEntity="DummyPassenger")
44+
* @ORM\JoinColumn(name="passenger_id", referencedColumnName="id")
45+
*/
46+
public $passenger;
47+
48+
public function getId()
49+
{
50+
return $this->id;
51+
}
52+
}

0 commit comments

Comments
 (0)