Skip to content

Commit 93e45fc

Browse files
#4041 bugfix: FilterEagerLoadingExtension foreign identifier handling (#4042)
* Issue #4041 bugfix: FilterEagerLoadingExtension foreign identifier handling * test: add a unit test and use a foreign id for DummyCar * chore: add changelog entry Co-authored-by: Alan Poulain <[email protected]>
1 parent 17d92a7 commit 93e45fc

File tree

8 files changed

+98
-8
lines changed

8 files changed

+98
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## 2.6.3
44

55
* Mercure: Do not use data in options when deleting (#4056)
6+
* Doctrine: Support for foreign identifiers
67

78
## 2.6.2
89

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,16 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
7676
if (!$classMetadata->isIdentifierComposite) {
7777
$replacementAlias = $queryNameGenerator->generateJoinAlias($originAlias);
7878
$in = $this->getQueryBuilderWithNewAliases($queryBuilder, $queryNameGenerator, $originAlias, $replacementAlias);
79-
$in->select($replacementAlias);
80-
$queryBuilderClone->andWhere($queryBuilderClone->expr()->in($originAlias, $in->getDQL()));
79+
80+
if ($classMetadata->containsForeignIdentifier) {
81+
$identifier = current($classMetadata->getIdentifier());
82+
$in->select("IDENTITY($replacementAlias.$identifier)");
83+
$queryBuilderClone->andWhere($queryBuilderClone->expr()->in("$originAlias.$identifier", $in->getDQL()));
84+
} else {
85+
$in->select($replacementAlias);
86+
$queryBuilderClone->andWhere($queryBuilderClone->expr()->in($originAlias, $in->getDQL()));
87+
}
88+
8189
$changedWhereClause = true;
8290
} else {
8391
// 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

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ public function applyToCollection(QueryBuilder $queryBuilder, QueryNameGenerator
8080
}
8181

8282
if (null !== $this->order) {
83+
// A foreign identifier cannot be used for ordering.
84+
if ($classMetaData->containsForeignIdentifier) {
85+
return;
86+
}
87+
8388
foreach ($identifiers as $identifier) {
8489
$queryBuilder->addOrderBy("{$rootAlias}.{$identifier}", $this->order);
8590
}

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,48 @@ public function testCompositeIdentifiersWithoutAssociation()
559559
$this->assertEquals($this->toDQLString($expected), $qb->getDQL());
560560
}
561561

562+
public function testCompositeIdentifiersWithForeignIdentifiers()
563+
{
564+
$resourceMetadataFactoryProphecy = $this->prophesize(ResourceMetadataFactoryInterface::class);
565+
$resourceMetadataFactoryProphecy->create(DummyCar::class)->willReturn(new ResourceMetadata(DummyCar::class));
566+
567+
$classMetadata = new ClassMetadataInfo(DummyCar::class);
568+
$classMetadata->setIdentifier(['id']);
569+
$classMetadata->containsForeignIdentifier = true;
570+
571+
$em = $this->prophesize(EntityManager::class);
572+
$em->getExpressionBuilder()->shouldBeCalled()->willReturn(new Expr());
573+
$em->getClassMetadata(DummyCar::class)->shouldBeCalled()->willReturn($classMetadata);
574+
575+
$qb = new QueryBuilder($em->reveal());
576+
577+
$qb->select('o')
578+
->from(DummyCar::class, 'o')
579+
->leftJoin('o.colors', 'colors')
580+
->where('o.colors = :foo')
581+
->setParameter('foo', 1);
582+
583+
$queryNameGenerator = $this->prophesize(QueryNameGeneratorInterface::class);
584+
$queryNameGenerator->generateJoinAlias('colors')->shouldBeCalled()->willReturn('colors_2');
585+
$queryNameGenerator->generateJoinAlias('o')->shouldBeCalled()->willReturn('o_2');
586+
587+
$filterEagerLoadingExtension = new FilterEagerLoadingExtension($resourceMetadataFactoryProphecy->reveal(), true);
588+
$filterEagerLoadingExtension->applyToCollection($qb, $queryNameGenerator->reveal(), DummyCar::class, 'get');
589+
590+
$expected = <<<SQL
591+
SELECT o
592+
FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o
593+
LEFT JOIN o.colors colors
594+
WHERE o.id IN(
595+
SELECT IDENTITY(o_2.id) FROM ApiPlatform\Core\Tests\Fixtures\TestBundle\Entity\DummyCar o_2
596+
LEFT JOIN o_2.colors colors_2
597+
WHERE o_2.colors = :foo
598+
)
599+
SQL;
600+
601+
$this->assertEquals($this->toDQLString($expected), $qb->getDQL());
602+
}
603+
562604
private function toDQLString(string $dql): string
563605
{
564606
return preg_replace(['/\s+/', '/\(\s/', '/\s\)/'], [' ', '(', ')'], $dql);

tests/Fixtures/TestBundle/Entity/DummyCar.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,10 @@
4242
class DummyCar
4343
{
4444
/**
45-
* @var int The entity Id
45+
* @var DummyCarIdentifier The entity Id
4646
*
4747
* @ORM\Id
48-
* @ORM\GeneratedValue
49-
* @ORM\Column(type="integer")
48+
* @ORM\OneToOne(targetEntity="DummyCarIdentifier", cascade="persist")
5049
*/
5150
private $id;
5251

@@ -85,7 +84,7 @@ class DummyCar
8584
*
8685
* @ORM\ManyToMany(targetEntity="UuidIdentifierDummy", indexBy="uuid")
8786
* * @ORM\JoinTable(name="uuid_cars",
88-
* joinColumns={@ORM\JoinColumn(name="car_id", referencedColumnName="id")},
87+
* joinColumns={@ORM\JoinColumn(name="car_id", referencedColumnName="id_id")},
8988
* inverseJoinColumns={@ORM\JoinColumn(name="uuid_uuid", referencedColumnName="uuid")}
9089
* )
9190
* @Serializer\Groups({"colors"})
@@ -127,6 +126,7 @@ class DummyCar
127126

128127
public function __construct()
129128
{
129+
$this->id = new DummyCarIdentifier();
130130
$this->colors = new ArrayCollection();
131131
}
132132

tests/Fixtures/TestBundle/Entity/DummyCarColor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class DummyCarColor
3939
* @var DummyCar
4040
*
4141
* @ORM\ManyToOne(targetEntity="DummyCar", inversedBy="colors")
42-
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE")
42+
* @ORM\JoinColumn(nullable=false, onDelete="CASCADE", referencedColumnName="id_id")
4343
* @Assert\NotBlank
4444
*/
4545
private $car;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 Doctrine\ORM\Mapping as ORM;
17+
18+
/**
19+
* @ORM\Entity
20+
*/
21+
class DummyCarIdentifier
22+
{
23+
/**
24+
* @ORM\Id
25+
* @ORM\GeneratedValue
26+
* @ORM\Column(type="integer")
27+
*/
28+
private $id;
29+
30+
public function __toString()
31+
{
32+
return (string) $this->id;
33+
}
34+
}

tests/Fixtures/TestBundle/Entity/DummyTravel.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class DummyTravel
3131

3232
/**
3333
* @ORM\ManyToOne(targetEntity="DummyCar")
34-
* @ORM\JoinColumn(name="car_id", referencedColumnName="id")
34+
* @ORM\JoinColumn(name="car_id", referencedColumnName="id_id")
3535
*/
3636
public $car;
3737

0 commit comments

Comments
 (0)