Completed
Pull Request — master (#6500)
by Mathew
17:49 queued 08:41
created

LimitSubqueryOutputWalker   F

Complexity

Total Complexity 62

Size/Duplication

Total Lines 542
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 92.97%

Importance

Changes 0
Metric Value
wmc 62
lcom 1
cbo 17
dl 0
loc 542
ccs 172
cts 185
cp 0.9297
rs 3.5483
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 1
B platformSupportsRowNumber() 0 10 7
B rebuildOrderByForRowNumber() 0 22 5
A walkSelectStatement() 0 7 2
B walkSelectStatementWithoutRowNumber() 0 44 4
C addMissingItemsFromOrderByToSelect() 0 49 8
A getOrderByPathExpressions() 0 4 1
A getInnerSQL() 0 21 3
C getSQLIdentifier() 0 53 11
A walkPathExpression() 0 8 4
B walkSelectStatementWithRowNumber() 0 47 5
A preserveSqlOrdering() 0 22 2
C rebuildOrderByClauseForOuterScope() 0 67 8
A walkSubSelect() 0 10 1

How to fix   Complexity   

Complex Class

Complex classes like LimitSubqueryOutputWalker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LimitSubqueryOutputWalker, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Tools\Pagination;
21
22
use Doctrine\DBAL\Platforms\DB2Platform;
23
use Doctrine\DBAL\Platforms\OraclePlatform;
24
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
25
use Doctrine\DBAL\Platforms\SQLAnywherePlatform;
26
use Doctrine\DBAL\Platforms\SQLServerPlatform;
27
use Doctrine\ORM\Query\AST\OrderByClause;
28
use Doctrine\ORM\Query\AST\PartialObjectExpression;
29
use Doctrine\ORM\Query\AST\SelectExpression;
30
use Doctrine\ORM\Query\SqlWalker;
31
use Doctrine\ORM\Query\AST\SelectStatement;
32
33
/**
34
 * Wraps the query in order to select root entity IDs for pagination.
35
 *
36
 * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
37
 * SELECT DISTINCT <id> FROM (<original SQL>) LIMIT x OFFSET y
38
 *
39
 * Works with composite keys but cannot deal with queries that have multiple
40
 * root entities (e.g. `SELECT f, b from Foo, Bar`)
41
 *
42
 * @author Sander Marechal <[email protected]>
43
 */
44
class LimitSubqueryOutputWalker extends SqlWalker
45
{
46
    /**
47
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
48
     */
49
    private $platform;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
50
51
    /**
52
     * @var \Doctrine\ORM\Query\ResultSetMapping
53
     */
54
    private $rsm;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
55
56
    /**
57
     * @var array
58
     */
59
    private $queryComponents;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
60
61
    /**
62
     * @var int
63
     */
64
    private $firstResult;
65
66
    /**
67
     * @var int
68
     */
69
    private $maxResults;
70
71
    /**
72
     * @var \Doctrine\ORM\EntityManager
73
     */
74
    private $em;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
75
76
    /**
77
     * The quote strategy.
78
     *
79
     * @var \Doctrine\ORM\Mapping\QuoteStrategy
80
     */
81
    private $quoteStrategy;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
82
83
    /**
84
     * @var array
85
     */
86
    private $orderByPathExpressions = [];
87
88
    /**
89
     * @var bool We don't want to add path expressions from sub-selects into the select clause of the containing query.
90
     *           This state flag simply keeps track on whether we are walking on a subquery or not
91
     */
92
    private $inSubSelect = false;
93
94
    /**
95
     * Constructor.
96
     *
97
     * Stores various parameters that are otherwise unavailable
98
     * because Doctrine\ORM\Query\SqlWalker keeps everything private without
99
     * accessors.
100
     *
101
     * @param \Doctrine\ORM\Query              $query
102
     * @param \Doctrine\ORM\Query\ParserResult $parserResult
103
     * @param array                            $queryComponents
104
     */
105 51
    public function __construct($query, $parserResult, array $queryComponents)
106
    {
107 51
        $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
108 51
        $this->rsm = $parserResult->getResultSetMapping();
109 51
        $this->queryComponents = $queryComponents;
110
111
        // Reset limit and offset
112 51
        $this->firstResult = $query->getFirstResult();
113 51
        $this->maxResults = $query->getMaxResults();
114 51
        $query->setFirstResult(null)->setMaxResults(null);
115
116 51
        $this->em               = $query->getEntityManager();
117 51
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
118
119 51
        parent::__construct($query, $parserResult, $queryComponents);
120 51
    }
121
122
    /**
123
     * Check if the platform supports the ROW_NUMBER window function.
124
     *
125
     * @return bool
126
     */
127 51
    private function platformSupportsRowNumber()
128
    {
129 51
        return $this->platform instanceof PostgreSqlPlatform
130 49
            || $this->platform instanceof SQLServerPlatform
131 49
            || $this->platform instanceof OraclePlatform
132 46
            || $this->platform instanceof SQLAnywherePlatform
133 46
            || $this->platform instanceof DB2Platform
134 46
            || (method_exists($this->platform, 'supportsRowNumberFunction')
135 51
                && $this->platform->supportsRowNumberFunction());
0 ignored issues
show
Bug introduced by
The method supportsRowNumberFunction() does not seem to exist on object<Doctrine\DBAL\Platforms\AbstractPlatform>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
136
    }
137
138
    /**
139
     * Rebuilds a select statement's order by clause for use in a
140
     * ROW_NUMBER() OVER() expression.
141
     *
142
     * @param SelectStatement $AST
143
     */
144 3
    private function rebuildOrderByForRowNumber(SelectStatement $AST)
145
    {
146 3
        $orderByClause = $AST->orderByClause;
147 3
        $selectAliasToExpressionMap = [];
148
        // Get any aliases that are available for select expressions.
149 3
        foreach ($AST->selectClause->selectExpressions as $selectExpression) {
150 3
            $selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
151
        }
152
153
        // Rebuild string orderby expressions to use the select expression they're referencing
154 3
        foreach ($orderByClause->orderByItems as $orderByItem) {
155 3
            if (is_string($orderByItem->expression) && isset($selectAliasToExpressionMap[$orderByItem->expression])) {
156 3
                $orderByItem->expression = $selectAliasToExpressionMap[$orderByItem->expression];
157
            }
158
        }
159 3
        $func = new RowNumberOverFunction('dctrn_rownum');
160 3
        $func->orderByClause = $AST->orderByClause;
161 3
        $AST->selectClause->selectExpressions[] = new SelectExpression($func, 'dctrn_rownum', true);
162
163
        // No need for an order by clause, we'll order by rownum in the outer query.
164 3
        $AST->orderByClause = null;
165 3
    }
166
167
    /**
168
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
169
     *
170
     * @param SelectStatement $AST
171
     *
172
     * @return string
173
     *
174
     * @throws \RuntimeException
175
     */
176 51
    public function walkSelectStatement(SelectStatement $AST)
177
    {
178 51
        if ($this->platformSupportsRowNumber()) {
179 5
            return $this->walkSelectStatementWithRowNumber($AST);
180
        }
181 46
        return $this->walkSelectStatementWithoutRowNumber($AST);
182
    }
183
184
    /**
185
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
186
     * This method is for use with platforms which support ROW_NUMBER.
187
     *
188
     * @param SelectStatement $AST
189
     *
190
     * @return string
191
     *
192
     * @throws \RuntimeException
193
     */
194 5
    public function walkSelectStatementWithRowNumber(SelectStatement $AST)
195
    {
196 5
        $hasOrderBy = false;
197 5
        $outerOrderBy = ' ORDER BY dctrn_minrownum ASC';
198 5
        $orderGroupBy = '';
199 5
        if ($AST->orderByClause instanceof OrderByClause) {
200 3
            $hasOrderBy = true;
201 3
            $this->rebuildOrderByForRowNumber($AST);
202
        }
203
204 5
        $innerSql = $this->getInnerSQL($AST);
205
206 5
        $sqlIdentifier = $this->getSQLIdentifier($AST);
207
208 5
        if ($hasOrderBy) {
209 3
            $orderGroupBy = ' GROUP BY ' . implode(', ', $sqlIdentifier);
210 3
            $sqlIdentifier[] = 'MIN(' . $this->walkResultVariable('dctrn_rownum') . ') AS dctrn_minrownum';
211
        }
212
213
        // Build the counter query
214 5
        $sql = sprintf(
215 5
            'SELECT DISTINCT %s FROM (%s) dctrn_result',
216 5
            implode(', ', $sqlIdentifier),
217 5
            $innerSql
218
        );
219
220 5
        if ($hasOrderBy) {
221 3
            $sql .= $orderGroupBy . $outerOrderBy;
222
        }
223
224
        // Apply the limit and offset.
225 5
        $sql = $this->platform->modifyLimitQuery(
226
            $sql,
227 5
            $this->maxResults,
228 5
            $this->firstResult
229
        );
230
231
        // Add the columns to the ResultSetMapping. It's not really nice but
232
        // it works. Preferably I'd clear the RSM or simply create a new one
233
        // but that is not possible from inside the output walker, so we dirty
234
        // up the one we have.
235 5
        foreach ($sqlIdentifier as $property => $alias) {
236 5
            $this->rsm->addScalarResult($alias, $property);
237
        }
238
239 5
        return $sql;
240
    }
241
242
    /**
243
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
244
     * This method is for platforms which DO NOT support ROW_NUMBER.
245
     *
246
     * @param SelectStatement $AST
247
     * @param bool $addMissingItemsFromOrderByToSelect
248
     *
249
     * @return string
250
     *
251
     * @throws \RuntimeException
252
     */
253 46
    public function walkSelectStatementWithoutRowNumber(SelectStatement $AST, $addMissingItemsFromOrderByToSelect = true)
254
    {
255
        // We don't want to call this recursively!
256 46
        if ($AST->orderByClause instanceof OrderByClause && $addMissingItemsFromOrderByToSelect) {
257
            // In the case of ordering a query by columns from joined tables, we
258
            // must add those columns to the select clause of the query BEFORE
259
            // the SQL is generated.
260 40
            $this->addMissingItemsFromOrderByToSelect($AST);
261
        }
262
263
        // Remove order by clause from the inner query
264
        // It will be re-appended in the outer select generated by this method
265 46
        $orderByClause = $AST->orderByClause;
266 46
        $AST->orderByClause = null;
267
268 46
        $innerSql = $this->getInnerSQL($AST);
269
270 46
        $sqlIdentifier = $this->getSQLIdentifier($AST);
271
272
        // Build the counter query
273 46
        $sql = sprintf('SELECT DISTINCT %s FROM (%s) dctrn_result',
274 46
            implode(', ', $sqlIdentifier), $innerSql);
275
276
        // http://www.doctrine-project.org/jira/browse/DDC-1958
277 46
        $sql = $this->preserveSqlOrdering($sqlIdentifier, $innerSql, $sql, $orderByClause);
0 ignored issues
show
Bug introduced by
It seems like $orderByClause defined by $AST->orderByClause on line 265 can be null; however, Doctrine\ORM\Tools\Pagin...::preserveSqlOrdering() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
278
279
        // Apply the limit and offset.
280 46
        $sql = $this->platform->modifyLimitQuery(
281 46
            $sql, $this->maxResults, $this->firstResult
282
        );
283
284
        // Add the columns to the ResultSetMapping. It's not really nice but
285
        // it works. Preferably I'd clear the RSM or simply create a new one
286
        // but that is not possible from inside the output walker, so we dirty
287
        // up the one we have.
288 46
        foreach ($sqlIdentifier as $property => $alias) {
289 46
            $this->rsm->addScalarResult($alias, $property);
290
        }
291
292
        // Restore orderByClause
293 46
        $AST->orderByClause = $orderByClause;
294
295 46
        return $sql;
296
    }
297
298
    /**
299
     * Finds all PathExpressions in an AST's OrderByClause, and ensures that
300
     * the referenced fields are present in the SelectClause of the passed AST.
301
     *
302
     * @param SelectStatement $AST
303
     */
304 40
    private function addMissingItemsFromOrderByToSelect(SelectStatement $AST)
305
    {
306 40
        $this->orderByPathExpressions = [];
307
308
        // We need to do this in another walker because otherwise we'll end up
309
        // polluting the state of this one.
310 40
        $walker = clone $this;
311
312
        // This will populate $orderByPathExpressions via
313
        // LimitSubqueryOutputWalker::walkPathExpression, which will be called
314
        // as the select statement is walked. We'll end up with an array of all
315
        // path expressions referenced in the query.
316 40
        $walker->walkSelectStatementWithoutRowNumber($AST, false);
317 40
        $orderByPathExpressions = $walker->getOrderByPathExpressions();
318
319
        // Get a map of referenced identifiers to field names.
320 40
        $selects = [];
321 40
        foreach ($orderByPathExpressions as $pathExpression) {
322 38
            $idVar = $pathExpression->identificationVariable;
323 38
            $field = $pathExpression->field;
324 38
            if (!isset($selects[$idVar])) {
325 38
                $selects[$idVar] = [];
326
            }
327 38
            $selects[$idVar][$field] = true;
328
        }
329
330
        // Loop the select clause of the AST and exclude items from $select
331
        // that are already being selected in the query.
332 40
        foreach ($AST->selectClause->selectExpressions as $selectExpression) {
333 40
            if ($selectExpression instanceof SelectExpression) {
334 40
                $idVar = $selectExpression->expression;
335 40
                if (!is_string($idVar)) {
336 2
                    continue;
337
                }
338 40
                $field = $selectExpression->fieldIdentificationVariable;
339 40
                if ($field === null) {
340
                    // No need to add this select, as we're already fetching the whole object.
341 40
                    unset($selects[$idVar]);
342
                } else {
343 40
                    unset($selects[$idVar][$field]);
344
                }
345
            }
346
        }
347
348
        // Add select items which were not excluded to the AST's select clause.
349 40
        foreach ($selects as $idVar => $fields) {
350 9
            $AST->selectClause->selectExpressions[] = new SelectExpression(new PartialObjectExpression($idVar, array_keys($fields)), null, true);
351
        }
352 40
    }
353
354
    /**
355
     * Generates new SQL for statements with an order by clause
356
     *
357
     * @param array           $sqlIdentifier
358
     * @param string          $innerSql
359
     * @param string          $sql
360
     * @param OrderByClause   $orderByClause
361
     *
362
     * @return string
363
     */
364 46
    private function preserveSqlOrdering(array $sqlIdentifier, $innerSql, $sql, $orderByClause)
365
    {
366
        // If the sql statement has an order by clause, we need to wrap it in a new select distinct
367
        // statement
368 46
        if (! $orderByClause instanceof OrderByClause) {
369 6
            return $sql;
370
        }
371
372
        // Rebuild the order by clause to work in the scope of the new select statement
373
        /* @var array $orderBy an array of rebuilt order by items */
374 40
        $orderBy = $this->rebuildOrderByClauseForOuterScope($orderByClause);
375
376
        // Build the select distinct statement
377 40
        $sql = sprintf(
378 40
            'SELECT DISTINCT %s FROM (%s) dctrn_result ORDER BY %s',
379 40
            implode(', ', $sqlIdentifier),
380
            $innerSql,
381 40
            implode(', ', $orderBy)
382
        );
383
384 40
        return $sql;
385
    }
386
387
    /**
388
     * Generates a new order by clause that works in the scope of a select query wrapping the original
389
     *
390
     * @param OrderByClause $orderByClause
391
     * @return array
392
     */
393 40
    private function rebuildOrderByClauseForOuterScope(OrderByClause $orderByClause)
394
    {
395
        $dqlAliasToSqlTableAliasMap
396
            = $searchPatterns
397
            = $replacements
398
            = $dqlAliasToClassMap
399
            = $selectListAdditions
0 ignored issues
show
Unused Code introduced by
$selectListAdditions is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
400
            = $orderByItems
401 40
            = [];
402
403
        // Generate DQL alias -> SQL table alias mapping
404 40
        foreach(array_keys($this->rsm->aliasMap) as $dqlAlias) {
405 40
            $dqlAliasToClassMap[$dqlAlias] = $class = $this->queryComponents[$dqlAlias]['metadata'];
406 40
            $dqlAliasToSqlTableAliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
407
        }
408
409
        // Pattern to find table path expressions in the order by clause
410 40
        $fieldSearchPattern = '/(?<![a-z0-9_])%s\.%s(?![a-z0-9_])/i';
411
412
        // Generate search patterns for each field's path expression in the order by clause
413 40
        foreach($this->rsm->fieldMappings as $fieldAlias => $fieldName) {
414 40
            $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias];
415 40
            $class = $dqlAliasToClassMap[$dqlAliasForFieldAlias];
416
417
            // If the field is from a joined child table, we won't be ordering
418
            // on it.
419 40
            if (!isset($class->fieldMappings[$fieldName])) {
420 1
                continue;
421
            }
422
423 40
            $fieldMapping = $class->fieldMappings[$fieldName];
424
425
            // Get the proper column name as will appear in the select list
426 40
            $columnName = $this->quoteStrategy->getColumnName(
427
                $fieldName,
428 40
                $dqlAliasToClassMap[$dqlAliasForFieldAlias],
429 40
                $this->em->getConnection()->getDatabasePlatform()
430
            );
431
432
            // Get the SQL table alias for the entity and field
433 40
            $sqlTableAliasForFieldAlias = $dqlAliasToSqlTableAliasMap[$dqlAliasForFieldAlias];
434 40
            if (isset($fieldMapping['declared']) && $fieldMapping['declared'] !== $class->name) {
435
                // Field was declared in a parent class, so we need to get the proper SQL table alias
436
                // for the joined parent table.
437 2
                $otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']);
438 2
                if (!$otherClassMetadata->isMappedSuperclass) {
439 1
                    $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
440
                }
441
            }
442
443
            // Compose search/replace patterns
444 40
            $searchPatterns[] = sprintf($fieldSearchPattern, $sqlTableAliasForFieldAlias, $columnName);
445 40
            $replacements[] = $fieldAlias;
446
        }
447
448 40
        foreach($orderByClause->orderByItems as $orderByItem) {
449
            // Walk order by item to get string representation of it
450 40
            $orderByItemString = $this->walkOrderByItem($orderByItem);
451
452
            // Replace path expressions in the order by clause with their column alias
453 40
            $orderByItemString = preg_replace($searchPatterns, $replacements, $orderByItemString);
454
455 40
            $orderByItems[] = $orderByItemString;
456
        }
457
458 40
        return $orderByItems;
459
    }
460
461
    /**
462
     * getter for $orderByPathExpressions
463
     *
464
     * @return array
465
     */
466 40
    public function getOrderByPathExpressions()
467
    {
468 40
        return $this->orderByPathExpressions;
469
    }
470
471
    /**
472
     * @param SelectStatement $AST
473
     *
474
     * @return string
475
     *
476
     * @throws \Doctrine\ORM\OptimisticLockException
477
     * @throws \Doctrine\ORM\Query\QueryException
478
     */
479 51
    private function getInnerSQL(SelectStatement $AST)
480
    {
481
        // Set every select expression as visible(hidden = false) to
482
        // make $AST have scalar mappings properly - this is relevant for referencing selected
483
        // fields from outside the subquery, for example in the ORDER BY segment
484 51
        $hiddens = [];
485
486 51
        foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
487 51
            $hiddens[$idx] = $expr->hiddenAliasResultVariable;
488 51
            $expr->hiddenAliasResultVariable = false;
489
        }
490
491 51
        $innerSql = parent::walkSelectStatement($AST);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (walkSelectStatement() instead of getInnerSQL()). Are you sure this is correct? If so, you might want to change this to $this->walkSelectStatement().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
492
493
        // Restore hiddens
494 51
        foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
495 51
            $expr->hiddenAliasResultVariable = $hiddens[$idx];
496
        }
497
498 51
        return $innerSql;
499
    }
500
501
    /**
502
     * @param SelectStatement $AST
503
     *
504
     * @return array
505
     */
506 51
    private function getSQLIdentifier(SelectStatement $AST)
507
    {
508
        // Find out the SQL alias of the identifier column of the root entity.
509
        // It may be possible to make this work with multiple root entities but that
510
        // would probably require issuing multiple queries or doing a UNION SELECT.
511
        // So for now, it's not supported.
512
513
        // Get the root entity and alias from the AST fromClause.
514 51
        $from = $AST->fromClause->identificationVariableDeclarations;
515 51
        if (count($from) !== 1) {
516
            throw new \RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
517
        }
518
519 51
        $fromRoot       = reset($from);
520 51
        $rootAlias      = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
521 51
        $rootClass      = $this->queryComponents[$rootAlias]['metadata'];
522 51
        $rootIdentifier = $rootClass->identifier;
523
524
        // For every identifier, find out the SQL alias by combing through the ResultSetMapping
525 51
        $sqlIdentifier = [];
526 51
        foreach ($rootIdentifier as $property) {
527 51
            if (isset($rootClass->fieldMappings[$property])) {
528 50
                foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) {
529 50
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
530 50
                        $sqlIdentifier[$property] = $alias;
531
                    }
532
                }
533
            }
534
535 51
            if (isset($rootClass->associationMappings[$property])) {
536 1
                $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name'];
537
538 1
                foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) {
539 1
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
540 51
                        $sqlIdentifier[$property] = $alias;
541
                    }
542
                }
543
            }
544
        }
545
546 51
        if (count($sqlIdentifier) === 0) {
547
            throw new \RuntimeException('The Paginator does not support Queries which only yield ScalarResults.');
548
        }
549
550 51
        if (count($rootIdentifier) != count($sqlIdentifier)) {
551
            throw new \RuntimeException(sprintf(
552
                'Not all identifier properties can be found in the ResultSetMapping: %s',
553
                implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier)))
554
            ));
555
        }
556
557 51
        return $sqlIdentifier;
558
    }
559
560
    /**
561
     * {@inheritdoc}
562
     */
563 41
    public function walkPathExpression($pathExpr)
564
    {
565 41
        if (!$this->inSubSelect && !$this->platformSupportsRowNumber() && !in_array($pathExpr, $this->orderByPathExpressions)) {
566 38
            $this->orderByPathExpressions[] = $pathExpr;
567
        }
568
569 41
        return parent::walkPathExpression($pathExpr);
570
    }
571
572
    /**
573
     * {@inheritdoc}
574
     */
575
    public function walkSubSelect($subselect)
576
    {
577
        $this->inSubSelect = true;
578
579
        $sql = parent::walkSubselect($subselect);
580
581
        $this->inSubSelect = false;
582
583
        return $sql;
584
    }
585
}
586