Passed
Pull Request — 2.4 (#2639)
by Han Hui
04:15
created

QueryChecker::hasLeftJoin()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 4
nc 4
nop 1
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $selectAliases of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $orderByAliases of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $selectAliases of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
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