Skip to content

Commit d9e9dc6

Browse files
pmattmannBacLuc
authored andcommitted
FilterEagerLoadingsExtension: Check if exists
Where-Clause on a ToMany-Association
1 parent a386d32 commit d9e9dc6

File tree

1 file changed

+148
-0
lines changed

1 file changed

+148
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
namespace App\Doctrine\Orm\Extension;
4+
5+
use ApiPlatform\Doctrine\Orm\Extension\QueryCollectionExtensionInterface;
6+
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
7+
use ApiPlatform\Metadata\Exception\InvalidArgumentException;
8+
use ApiPlatform\Metadata\Operation;
9+
use Doctrine\ORM\EntityManagerInterface;
10+
use Doctrine\ORM\Mapping\ClassMetadata;
11+
use Doctrine\ORM\Mapping\ToManyInverseSideMapping;
12+
use Doctrine\ORM\Query\Expr\Andx;
13+
use Doctrine\ORM\Query\Expr\Comparison;
14+
use Doctrine\ORM\Query\Expr\Func;
15+
use Doctrine\ORM\Query\Expr\Join;
16+
use Doctrine\ORM\Query\Expr\Orx;
17+
use Doctrine\ORM\QueryBuilder;
18+
19+
final class FilterEagerLoadingsExtension implements QueryCollectionExtensionInterface {
20+
public function __construct(private QueryCollectionExtensionInterface $decorated) {}
21+
22+
public function applyToCollection(
23+
QueryBuilder $queryBuilder,
24+
QueryNameGeneratorInterface $queryNameGenerator,
25+
?string $resourceClass = null,
26+
?Operation $operation = null,
27+
array $context = []
28+
): void {
29+
if (null === $resourceClass) {
30+
throw new InvalidArgumentException('The "$resourceClass" parameter must not be null');
31+
}
32+
33+
$em = $queryBuilder->getEntityManager();
34+
$classMetadata = $em->getClassMetadata($resourceClass);
35+
36+
// If no where part, nothing to do
37+
$wherePart = $queryBuilder->getDQLPart('where');
38+
39+
if (!$wherePart) {
40+
return;
41+
}
42+
43+
$joinParts = $queryBuilder->getDQLPart('join');
44+
$originAlias = $queryBuilder->getRootAliases()[0];
45+
46+
if (!$joinParts || !isset($joinParts[$originAlias])) {
47+
return;
48+
}
49+
50+
$aliasMap = $this->buildAliasMap($em, $classMetadata, $joinParts, $originAlias);
51+
$usesToManyAlias = $this->usesAnyToMany($aliasMap, $wherePart);
52+
53+
if ($usesToManyAlias) {
54+
$this->decorated->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operation, $context);
55+
}
56+
}
57+
58+
private function buildAliasMap(EntityManagerInterface $em, ClassMetadata $classMetadata, array $joinParts, $originAlias) {
59+
// $alias => [ ClassMetadata $classMetadata, bool $is[One/Many]ToManyJoin ]
60+
$aliasMap = [$originAlias => [$classMetadata, false]];
61+
$joins = $joinParts[$originAlias];
62+
63+
foreach ($joins as $join) {
64+
// @var Join $join
65+
list($fromAlias, $fromProperty) = explode('.', $join->getJoin(), 2);
66+
$toAlias = $join->getAlias();
67+
68+
$fromClassMetadata = $aliasMap[$fromAlias][0];
69+
$association = $fromClassMetadata->getAssociationMapping($fromProperty);
70+
$m = $em->getClassMetadata($association['targetEntity']);
71+
72+
$isToMany = $aliasMap[$fromAlias][1] || ($association instanceof ToManyInverseSideMapping);
73+
74+
$aliasMap[$toAlias] = [$m, $isToMany];
75+
}
76+
77+
return $aliasMap;
78+
}
79+
80+
private function usesAnyToMany($toManyAliases, $wherePart) {
81+
if ($wherePart instanceof Andx) {
82+
return $this->usesAnyToManyAndx($toManyAliases, $wherePart);
83+
}
84+
if ($wherePart instanceof Orx) {
85+
return $this->usesAnyToManyOrx($toManyAliases, $wherePart);
86+
}
87+
if ($wherePart instanceof Comparison) {
88+
return $this->usesAnyToManyComparison($toManyAliases, $wherePart);
89+
}
90+
if ($wherePart instanceof Func) {
91+
return $this->usesAnyToManyFunc($toManyAliases, $wherePart);
92+
}
93+
if (is_string($wherePart)) {
94+
return $this->usesAnyToManyString($toManyAliases, $wherePart);
95+
}
96+
97+
throw new \Exception('Not Implemented: FilterEagerLoadingsExtension->usesToManyAlias for '.$wherePart);
98+
}
99+
100+
private function usesAnyToManyAndx($toManyAliases, Andx $and) {
101+
foreach ($and->getParts() as $part) {
102+
if ($this->usesAnyToMany($toManyAliases, $part)) {
103+
return true;
104+
}
105+
}
106+
107+
return false;
108+
}
109+
110+
private function usesAnyToManyOrx($toManyAliases, Orx $or) {
111+
foreach ($or->getParts() as $part) {
112+
if ($this->usesAnyToMany($toManyAliases, $part)) {
113+
return true;
114+
}
115+
}
116+
117+
return false;
118+
}
119+
120+
private function usesAnyToManyComparison($toManyAliases, Comparison $comparison) {
121+
return
122+
$this->usesAnyToMany($toManyAliases, $comparison->getLeftExpr())
123+
|| $this->usesAnyToMany($toManyAliases, $comparison->getRightExpr());
124+
}
125+
126+
private function usesAnyToManyFunc($toManyAliases, Func $func) {
127+
foreach ($func->getArguments() as $argument) {
128+
if ($this->usesAnyToMany($toManyAliases, $argument)) {
129+
return true;
130+
}
131+
}
132+
133+
return false;
134+
}
135+
136+
private function usesAnyToManyString($toManyAliases, string $comparison) {
137+
$elements = explode('.', $comparison, 2);
138+
139+
if (2 == count($elements)) {
140+
$alias = $elements[0];
141+
if (isset($toManyAliases[$alias])) {
142+
return $toManyAliases[$alias][1];
143+
}
144+
}
145+
146+
return false;
147+
}
148+
}

0 commit comments

Comments
 (0)