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\Bridge\Doctrine\Orm\Util; |
15
|
|
|
|
16
|
|
|
use Doctrine\Common\Persistence\ManagerRegistry; |
17
|
|
|
use Doctrine\ORM\Mapping\ClassMetadata; |
18
|
|
|
use Doctrine\ORM\Query\Expr\Join; |
19
|
|
|
use Doctrine\ORM\QueryBuilder; |
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Utility functions for working with Doctrine ORM query. |
23
|
|
|
* |
24
|
|
|
* @author Teoh Han Hui <[email protected]> |
25
|
|
|
* @author Vincent Chalamon <[email protected]> |
26
|
|
|
* |
27
|
|
|
* @internal |
28
|
|
|
*/ |
29
|
|
|
final class QueryChecker |
30
|
|
|
{ |
31
|
|
|
private function __construct() |
32
|
|
|
{ |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Determines whether the query builder uses a HAVING clause. |
37
|
|
|
*/ |
38
|
|
|
public static function hasHavingClause(QueryBuilder $queryBuilder): bool |
39
|
|
|
{ |
40
|
|
|
return null !== $queryBuilder->getDQLPart('having'); |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* Determines whether the query builder has any root entity with foreign key identifier. |
45
|
|
|
*/ |
46
|
|
|
public static function hasRootEntityWithForeignKeyIdentifier(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool |
47
|
|
|
{ |
48
|
|
|
foreach ($queryBuilder->getRootEntities() as $rootEntity) { |
49
|
|
|
/** @var ClassMetadata $rootMetadata */ |
50
|
|
|
$rootMetadata = $managerRegistry |
51
|
|
|
->getManagerForClass($rootEntity) |
52
|
|
|
->getClassMetadata($rootEntity); |
53
|
|
|
|
54
|
|
|
if ($rootMetadata->containsForeignIdentifier) { |
55
|
|
|
return true; |
56
|
|
|
} |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
return false; |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Determines whether the query builder has any composite identifier. |
64
|
|
|
*/ |
65
|
|
|
public static function hasRootEntityWithCompositeIdentifier(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool |
66
|
|
|
{ |
67
|
|
|
foreach ($queryBuilder->getRootEntities() as $rootEntity) { |
68
|
|
|
/** @var ClassMetadata $rootMetadata */ |
69
|
|
|
$rootMetadata = $managerRegistry |
70
|
|
|
->getManagerForClass($rootEntity) |
71
|
|
|
->getClassMetadata($rootEntity); |
72
|
|
|
|
73
|
|
|
if ($rootMetadata->isIdentifierComposite) { |
74
|
|
|
return true; |
75
|
|
|
} |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
return false; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Determines whether the query builder has a limit on the maximum number of results. |
83
|
|
|
*/ |
84
|
|
|
public static function hasMaxResults(QueryBuilder $queryBuilder): bool |
85
|
|
|
{ |
86
|
|
|
return null !== $queryBuilder->getMaxResults(); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
/** |
90
|
|
|
* Determines whether the query builder has ORDER BY on a column from a fetch joined to-many association. |
91
|
|
|
*/ |
92
|
|
|
public static function hasOrderByOnFetchJoinedToManyAssociation(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool |
93
|
|
|
{ |
94
|
|
|
if ( |
95
|
|
|
!($selectParts = $queryBuilder->getDQLPart('select')) || |
96
|
|
|
!$queryBuilder->getDQLPart('join') || |
97
|
|
|
!($orderByParts = $queryBuilder->getDQLPart('orderBy')) |
98
|
|
|
) { |
99
|
|
|
return false; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$rootAliases = $queryBuilder->getRootAliases(); |
103
|
|
|
|
104
|
|
|
$selectAliases = []; |
105
|
|
|
|
106
|
|
|
foreach ($selectParts as $select) { |
107
|
|
|
foreach ($select->getParts() as $part) { |
108
|
|
|
[$alias] = explode('.', $part); |
109
|
|
|
|
110
|
|
|
$selectAliases[] = $alias; |
111
|
|
|
} |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
if (!$selectAliases) { |
115
|
|
|
return false; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$selectAliases = array_diff($selectAliases, $rootAliases); |
119
|
|
|
if (!$selectAliases) { |
|
|
|
|
120
|
|
|
return false; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
$orderByAliases = []; |
124
|
|
|
|
125
|
|
|
foreach ($orderByParts as $orderBy) { |
126
|
|
|
foreach ($orderBy->getParts() as $part) { |
127
|
|
|
if (false !== strpos($part, '.')) { |
128
|
|
|
[$alias] = explode('.', $part); |
129
|
|
|
|
130
|
|
|
$orderByAliases[] = $alias; |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
if (!$orderByAliases) { |
136
|
|
|
return false; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
$orderByAliases = array_diff($orderByAliases, $rootAliases); |
140
|
|
|
if (!$orderByAliases) { |
|
|
|
|
141
|
|
|
return false; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
foreach ($orderByAliases as $orderByAlias) { |
145
|
|
|
$inToManyContext = false; |
146
|
|
|
|
147
|
|
|
foreach (QueryBuilderHelper::traverseJoins($orderByAlias, $queryBuilder, $managerRegistry) as $alias => [$metadata, $association]) { |
148
|
|
|
if ($inToManyContext && \in_array($alias, $selectAliases, true)) { |
149
|
|
|
return true; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
if (null !== $association && $metadata->isCollectionValuedAssociation($association)) { |
153
|
|
|
$inToManyContext = true; |
154
|
|
|
} |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
return false; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* Determines whether the query builder has ORDER BY on a column from a fetch joined to-many association. |
163
|
|
|
* |
164
|
|
|
* @deprecated |
165
|
|
|
*/ |
166
|
|
|
public static function hasOrderByOnToManyJoin(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool |
167
|
|
|
{ |
168
|
|
|
@trigger_error(sprintf('The use of "%s::hasOrderByOnToManyJoin()" is deprecated since 2.4 and will be removed in 3.0. Use "%1$s::hasOrderByOnFetchJoinedToManyAssociation()" instead.', __CLASS__), E_USER_DEPRECATED); |
169
|
|
|
|
170
|
|
|
return self::hasOrderByOnFetchJoinedToManyAssociation($queryBuilder, $managerRegistry); |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Determines whether the query builder already has a left join. |
175
|
|
|
*/ |
176
|
|
|
public static function hasLeftJoin(QueryBuilder $queryBuilder): bool |
177
|
|
|
{ |
178
|
|
|
foreach ($queryBuilder->getDQLPart('join') as $joins) { |
179
|
|
|
foreach ($joins as $join) { |
180
|
|
|
if (Join::LEFT_JOIN === $join->getJoinType()) { |
181
|
|
|
return true; |
182
|
|
|
} |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
return false; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
/** |
190
|
|
|
* Determines whether the query builder has a fetch joined to-many association. |
191
|
|
|
*/ |
192
|
|
|
public static function hasFetchJoinedToManyAssociation(QueryBuilder $queryBuilder, ManagerRegistry $managerRegistry): bool |
193
|
|
|
{ |
194
|
|
|
if ( |
195
|
|
|
!($selectParts = $queryBuilder->getDQLPart('select')) || |
196
|
|
|
!$queryBuilder->getDQLPart('join') |
197
|
|
|
) { |
198
|
|
|
return false; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
$rootAliases = $queryBuilder->getRootAliases(); |
202
|
|
|
|
203
|
|
|
$selectAliases = []; |
204
|
|
|
|
205
|
|
|
foreach ($selectParts as $select) { |
206
|
|
|
foreach ($select->getParts() as $part) { |
207
|
|
|
[$alias] = explode('.', $part); |
208
|
|
|
|
209
|
|
|
$selectAliases[] = $alias; |
210
|
|
|
} |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
if (!$selectAliases) { |
214
|
|
|
return false; |
215
|
|
|
} |
216
|
|
|
|
217
|
|
|
$selectAliases = array_diff($selectAliases, $rootAliases); |
218
|
|
|
if (!$selectAliases) { |
|
|
|
|
219
|
|
|
return false; |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
foreach ($selectAliases as $selectAlias) { |
223
|
|
|
foreach (QueryBuilderHelper::traverseJoins($selectAlias, $queryBuilder, $managerRegistry) as $alias => [$metadata, $association]) { |
224
|
|
|
if (null !== $association && $metadata->isCollectionValuedAssociation($association)) { |
225
|
|
|
return true; |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
} |
229
|
|
|
|
230
|
|
|
return false; |
231
|
|
|
} |
232
|
|
|
} |
233
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.