LimitSubqueryOutputWalker   F
last analyzed

Complexity

Total Complexity 64

Size/Duplication

Total Lines 559
Duplicated Lines 4.47 %

Coupling/Cohesion

Components 1
Dependencies 17

Importance

Changes 0
Metric Value
dl 25
loc 559
rs 3.4883
c 0
b 0
f 0
wmc 64
lcom 1
cbo 17

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 walkSelectStatementWithRowNumber() 0 47 5
B walkSelectStatementWithoutRowNumber() 0 44 4
C addMissingItemsFromOrderByToSelect() 0 49 8
B preserveSqlOrdering() 0 38 4
C rebuildOrderByClauseForOuterScope() 0 68 8
A getOrderByPathExpressions() 0 4 1
A getInnerSQL() 0 21 3
C getSQLIdentifier() 25 53 11
A walkPathExpression() 0 8 4
A walkSubSelect() 0 10 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
 * Doctrine ORM
4
 *
5
 * LICENSE
6
 *
7
 * This source file is subject to the new BSD license that is bundled
8
 * with this package in the file LICENSE.txt.
9
 * If you did not receive a copy of the license and are unable to
10
 * obtain it through the world-wide-web, please send an email
11
 * to [email protected] so I can send you a copy immediately.
12
 */
13
14
namespace Eccube\Doctrine\ORM\Tools\Pagination;
15
16
use Doctrine\DBAL\Platforms\DB2Platform;
17
use Doctrine\DBAL\Platforms\OraclePlatform;
18
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
19
use Doctrine\DBAL\Platforms\SQLAnywherePlatform;
20
use Doctrine\DBAL\Platforms\SQLServerPlatform;
21
use Doctrine\ORM\Query\AST\ArithmeticExpression;
22
use Doctrine\ORM\Query\AST\ArithmeticFactor;
23
use Doctrine\ORM\Query\AST\ArithmeticTerm;
24
use Doctrine\ORM\Query\AST\Literal;
25
use Doctrine\ORM\Query\AST\OrderByClause;
26
use Doctrine\ORM\Query\AST\OrderByItem;
27
use Doctrine\ORM\Query\AST\PartialObjectExpression;
28
use Doctrine\ORM\Query\AST\PathExpression;
29
use Doctrine\ORM\Query\AST\SelectExpression;
30
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
31
use Doctrine\ORM\Query\Expr\OrderBy;
32
use Doctrine\ORM\Query\Expr\Select;
33
use Doctrine\ORM\Query\SqlWalker;
34
use Doctrine\ORM\Query\AST\SelectStatement;
35
36
/**
37
 * Wraps the query in order to select root entity IDs for pagination.
38
 *
39
 * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
40
 * SELECT DISTINCT <id> FROM (<original SQL>) LIMIT x OFFSET y
41
 *
42
 * Works with composite keys but cannot deal with queries that have multiple
43
 * root entities (e.g. `SELECT f, b from Foo, Bar`)
44
 *
45
 * @author Sander Marechal <[email protected]>
46
 */
47
class LimitSubqueryOutputWalker extends SqlWalker
48
{
49
    /**
50
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
51
     */
52
    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...
53
54
    /**
55
     * @var \Doctrine\ORM\Query\ResultSetMapping
56
     */
57
    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...
58
59
    /**
60
     * @var array
61
     */
62
    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...
63
64
    /**
65
     * @var int
66
     */
67
    private $firstResult;
68
69
    /**
70
     * @var int
71
     */
72
    private $maxResults;
73
74
    /**
75
     * @var \Doctrine\ORM\EntityManager
76
     */
77
    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...
78
79
    /**
80
     * The quote strategy.
81
     *
82
     * @var \Doctrine\ORM\Mapping\QuoteStrategy
83
     */
84
    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...
85
86
    /**
87
     * @var array
88
     */
89
    private $orderByPathExpressions = array();
90
91
    /**
92
     * @var bool We don't want to add path expressions from sub-selects into the select clause of the containing query.
93
     *           This state flag simply keeps track on whether we are walking on a subquery or not
94
     */
95
    private $inSubSelect = false;
96
97
    /**
98
     * Constructor.
99
     *
100
     * Stores various parameters that are otherwise unavailable
101
     * because Doctrine\ORM\Query\SqlWalker keeps everything private without
102
     * accessors.
103
     *
104
     * @param \Doctrine\ORM\Query              $query
105
     * @param \Doctrine\ORM\Query\ParserResult $parserResult
106
     * @param array                            $queryComponents
107
     */
108
    public function __construct($query, $parserResult, array $queryComponents)
109
    {
110
        $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
111
        $this->rsm = $parserResult->getResultSetMapping();
112
        $this->queryComponents = $queryComponents;
113
114
        // Reset limit and offset
115
        $this->firstResult = $query->getFirstResult();
116
        $this->maxResults = $query->getMaxResults();
117
        $query->setFirstResult(null)->setMaxResults(null);
118
119
        $this->em               = $query->getEntityManager();
120
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
121
122
        parent::__construct($query, $parserResult, $queryComponents);
123
    }
124
125
    /**
126
     * Check if the platform supports the ROW_NUMBER window function.
127
     *
128
     * @return bool
129
     */
130
    private function platformSupportsRowNumber()
131
    {
132
        return $this->platform instanceof PostgreSqlPlatform
133
            || $this->platform instanceof SQLServerPlatform
134
            || $this->platform instanceof OraclePlatform
135
            || $this->platform instanceof SQLAnywherePlatform
136
            || $this->platform instanceof DB2Platform
137
            || (method_exists($this->platform, 'supportsRowNumberFunction')
138
                && $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...
139
    }
140
141
    /**
142
     * Rebuilds a select statement's order by clause for use in a
143
     * ROW_NUMBER() OVER() expression.
144
     *
145
     * @param SelectStatement $AST
146
     */
147
    private function rebuildOrderByForRowNumber(SelectStatement $AST)
148
    {
149
        $orderByClause = $AST->orderByClause;
150
        $selectAliasToExpressionMap = array();
151
        // Get any aliases that are available for select expressions.
152
        foreach ($AST->selectClause->selectExpressions as $selectExpression) {
153
            $selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
154
        }
155
156
        // Rebuild string orderby expressions to use the select expression they're referencing
157
        foreach ($orderByClause->orderByItems as $orderByItem) {
158
            if (is_string($orderByItem->expression) && isset($selectAliasToExpressionMap[$orderByItem->expression])) {
159
                $orderByItem->expression = $selectAliasToExpressionMap[$orderByItem->expression];
160
            }
161
        }
162
        $func = new RowNumberOverFunction('dctrn_rownum');
163
        $func->orderByClause = $AST->orderByClause;
164
        $AST->selectClause->selectExpressions[] = new SelectExpression($func, 'dctrn_rownum', true);
165
166
        // No need for an order by clause, we'll order by rownum in the outer query.
167
        $AST->orderByClause = null;
168
    }
169
170
    /**
171
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
172
     *
173
     * @param SelectStatement $AST
174
     *
175
     * @return string
176
     *
177
     * @throws \RuntimeException
178
     */
179
    public function walkSelectStatement(SelectStatement $AST)
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
180
    {
181
        if ($this->platformSupportsRowNumber()) {
182
            return $this->walkSelectStatementWithRowNumber($AST);
183
        }
184
        return $this->walkSelectStatementWithoutRowNumber($AST);
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
185
    }
186
187
    /**
188
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
189
     * This method is for use with platforms which support ROW_NUMBER.
190
     *
191
     * @param SelectStatement $AST
192
     *
193
     * @return string
194
     *
195
     * @throws \RuntimeException
196
     */
197
    public function walkSelectStatementWithRowNumber(SelectStatement $AST)
198
    {
199
        $hasOrderBy = false;
200
        $outerOrderBy = ' ORDER BY dctrn_minrownum ASC';
201
        $orderGroupBy = '';
202
        if ($AST->orderByClause instanceof OrderByClause) {
203
            $hasOrderBy = true;
204
            $this->rebuildOrderByForRowNumber($AST);
205
        }
206
207
        $innerSql = $this->getInnerSQL($AST);
208
209
        $sqlIdentifier = $this->getSQLIdentifier($AST);
210
211
        if ($hasOrderBy) {
212
            $orderGroupBy = ' GROUP BY ' . implode(', ', $sqlIdentifier);
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
213
            $sqlIdentifier[] = 'MIN(' . $this->walkResultVariable('dctrn_rownum') . ') AS dctrn_minrownum';
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
214
        }
215
216
        // Build the counter query
217
        $sql = sprintf(
218
            'SELECT DISTINCT %s FROM (%s) dctrn_result',
219
            implode(', ', $sqlIdentifier),
220
            $innerSql
221
        );
222
223
        if ($hasOrderBy) {
224
            $sql .= $orderGroupBy . $outerOrderBy;
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
225
        }
226
227
        // Apply the limit and offset.
228
        $sql = $this->platform->modifyLimitQuery(
229
            $sql,
230
            $this->maxResults,
231
            $this->firstResult
232
        );
233
234
        // Add the columns to the ResultSetMapping. It's not really nice but
235
        // it works. Preferably I'd clear the RSM or simply create a new one
236
        // but that is not possible from inside the output walker, so we dirty
237
        // up the one we have.
238
        foreach ($sqlIdentifier as $property => $alias) {
239
            $this->rsm->addScalarResult($alias, $property);
240
        }
241
242
        return $sql;
243
    }
244
245
    /**
246
     * Walks down a SelectStatement AST node, wrapping it in a SELECT DISTINCT.
247
     * This method is for platforms which DO NOT support ROW_NUMBER.
248
     *
249
     * @param SelectStatement $AST
250
     * @param bool $addMissingItemsFromOrderByToSelect
0 ignored issues
show
introduced by
Expected 12 spaces after parameter type; 1 found
Loading history...
251
     *
252
     * @return string
253
     *
254
     * @throws \RuntimeException
255
     */
256
    public function walkSelectStatementWithoutRowNumber(SelectStatement $AST, $addMissingItemsFromOrderByToSelect = true)
257
    {
258
        // We don't want to call this recursively!
259
        if ($AST->orderByClause instanceof OrderByClause && $addMissingItemsFromOrderByToSelect) {
260
            // In the case of ordering a query by columns from joined tables, we
261
            // must add those columns to the select clause of the query BEFORE
262
            // the SQL is generated.
263
            $this->addMissingItemsFromOrderByToSelect($AST);
264
        }
265
266
        // Remove order by clause from the inner query
267
        // It will be re-appended in the outer select generated by this method
268
        $orderByClause = $AST->orderByClause;
269
        $AST->orderByClause = null;
270
271
        $innerSql = $this->getInnerSQL($AST);
272
273
        $sqlIdentifier = $this->getSQLIdentifier($AST);
274
275
        // Build the counter query
276
        $sql = sprintf('SELECT DISTINCT %s FROM (%s) dctrn_result',
277
            implode(', ', $sqlIdentifier), $innerSql);
278
279
        // http://www.doctrine-project.org/jira/browse/DDC-1958
280
        $sql = $this->preserveSqlOrdering($sqlIdentifier, $innerSql, $sql, $orderByClause);
0 ignored issues
show
Bug introduced by
It seems like $orderByClause defined by $AST->orderByClause on line 268 can be null; however, Eccube\Doctrine\ORM\Tool...::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...
281
282
        // Apply the limit and offset.
283
        $sql = $this->platform->modifyLimitQuery(
284
            $sql, $this->maxResults, $this->firstResult
285
        );
286
287
        // Add the columns to the ResultSetMapping. It's not really nice but
288
        // it works. Preferably I'd clear the RSM or simply create a new one
289
        // but that is not possible from inside the output walker, so we dirty
290
        // up the one we have.
291
        foreach ($sqlIdentifier as $property => $alias) {
292
            $this->rsm->addScalarResult($alias, $property);
293
        }
294
295
        // Restore orderByClause
296
        $AST->orderByClause = $orderByClause;
297
298
        return $sql;
299
    }
300
301
    /**
302
     * Finds all PathExpressions in an AST's OrderByClause, and ensures that
303
     * the referenced fields are present in the SelectClause of the passed AST.
304
     *
305
     * @param SelectStatement $AST
306
     */
307
    private function addMissingItemsFromOrderByToSelect(SelectStatement $AST)
308
    {
309
        $this->orderByPathExpressions = array();
310
311
        // We need to do this in another walker because otherwise we'll end up
312
        // polluting the state of this one.
313
        $walker = clone $this;
314
315
        // This will populate $orderByPathExpressions via
316
        // LimitSubqueryOutputWalker::walkPathExpression, which will be called
317
        // as the select statement is walked. We'll end up with an array of all
318
        // path expressions referenced in the query.
319
        $walker->walkSelectStatementWithoutRowNumber($AST, false);
320
        $orderByPathExpressions = $walker->getOrderByPathExpressions();
321
322
        // Get a map of referenced identifiers to field names.
323
        $selects = array();
324
        foreach ($orderByPathExpressions as $pathExpression) {
325
            $idVar = $pathExpression->identificationVariable;
326
            $field = $pathExpression->field;
327
            if (!isset($selects[$idVar])) {
328
                $selects[$idVar] = array();
329
            }
330
            $selects[$idVar][$field] = true;
331
        }
332
333
        // Loop the select clause of the AST and exclude items from $select
334
        // that are already being selected in the query.
335
        foreach ($AST->selectClause->selectExpressions as $selectExpression) {
336
            if ($selectExpression instanceof SelectExpression) {
337
                $idVar = $selectExpression->expression;
338
                if (!is_string($idVar)) {
339
                    continue;
340
                }
341
                $field = $selectExpression->fieldIdentificationVariable;
342
                if ($field === null) {
343
                    // No need to add this select, as we're already fetching the whole object.
344
                    unset($selects[$idVar]);
345
                } else {
346
                    unset($selects[$idVar][$field]);
347
                }
348
            }
349
        }
350
351
        // Add select items which were not excluded to the AST's select clause.
352
        foreach ($selects as $idVar => $fields) {
353
            $AST->selectClause->selectExpressions[] = new SelectExpression(new PartialObjectExpression($idVar, array_keys($fields)), null, true);
354
        }
355
    }
356
357
    /**
358
     * Generates new SQL for statements with an order by clause
359
     *
360
     * @param array           $sqlIdentifier
361
     * @param string          $innerSql
362
     * @param string          $sql
363
     * @param OrderByClause   $orderByClause
364
     *
365
     * @return string
366
     */
367
    private function preserveSqlOrdering(array $sqlIdentifier, $innerSql, $sql, $orderByClause)
368
    {
369
        // If the sql statement has an order by clause, we need to wrap it in a new select distinct
370
        // statement
371
        if (! $orderByClause instanceof OrderByClause) {
372
            return $sql;
373
        }
374
375
        // Rebuild the order by clause to work in the scope of the new select statement
376
        /* @var array $orderBy an array of rebuilt order by items */
377
        $orderBy = $this->rebuildOrderByClauseForOuterScope($orderByClause);
378
379
        $innerSqlIdentifier = $sqlIdentifier;
380
381
        foreach ($orderBy as $field) {
382
            $field = preg_replace('/((\S+)\s+(ASC|DESC)\s*,?)*/', '${2}', $field);
383
384
            // skip fields that are selected by identifiers,
385
            // if those are ordered by in the query
386
            if (in_array($field, $sqlIdentifier, true)) {
387
                continue;
388
            }
389
            $innerSqlIdentifier[] = $field;
390
        }
391
392
        // Build the innner select statement
393
        $sql = sprintf(
394
            'SELECT DISTINCT %s FROM (%s) dctrn_result_inner ORDER BY %s',
395
            implode(', ', $innerSqlIdentifier),
396
            $innerSql,
397
            implode(', ', $orderBy)
398
        );
399
400
        // now only select distinct identifier
401
        $sql = sprintf('SELECT DISTINCT %s FROM (%s) dctrn_result', implode(', ', $sqlIdentifier), $sql);
402
403
        return $sql;
404
    }
405
406
    /**
407
     * Generates a new order by clause that works in the scope of a select query wrapping the original
408
     *
409
     * @param OrderByClause $orderByClause
410
     * @return array
411
     */
412
    private function rebuildOrderByClauseForOuterScope(OrderByClause $orderByClause)
413
    {
414
        $dqlAliasToSqlTableAliasMap
415
            = $searchPatterns
416
            = $replacements
417
            = $dqlAliasToClassMap
418
            = $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...
419
            = $orderByItems
420
            = array();
421
422
        // Generate DQL alias -> SQL table alias mapping
423
        foreach(array_keys($this->rsm->aliasMap) as $dqlAlias) {
424
            $dqlAliasToClassMap[$dqlAlias] = $class = $this->queryComponents[$dqlAlias]['metadata'];
425
            $dqlAliasToSqlTableAliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
426
        }
427
428
        // Pattern to find table path expressions in the order by clause
429
        $fieldSearchPattern = '/(?<![a-z0-9_])%s\.%s(?![a-z0-9_])/i';
430
431
        // Generate search patterns for each field's path expression in the order by clause
432
        foreach($this->rsm->fieldMappings as $fieldAlias => $fieldName) {
433
            $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias];
434
            $class = $dqlAliasToClassMap[$dqlAliasForFieldAlias];
435
436
            // If the field is from a joined child table, we won't be ordering
437
            // on it.
438
            if (!isset($class->fieldMappings[$fieldName])) {
439
                continue;
440
            }
441
442
            $fieldMapping = $class->fieldMappings[$fieldName];
443
444
            // Get the proper column name as will appear in the select list
445
            $columnName = $this->quoteStrategy->getColumnName(
446
                $fieldName,
447
                $dqlAliasToClassMap[$dqlAliasForFieldAlias],
448
                $this->em->getConnection()->getDatabasePlatform()
449
            );
450
451
            // Get the SQL table alias for the entity and field
452
            $sqlTableAliasForFieldAlias = $dqlAliasToSqlTableAliasMap[$dqlAliasForFieldAlias];
453
            if (isset($fieldMapping['declared']) && $fieldMapping['declared'] !== $class->name) {
454
                // Field was declared in a parent class, so we need to get the proper SQL table alias
455
                // for the joined parent table.
456
                $otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']);
457
                if (!$otherClassMetadata->isMappedSuperclass) {
458
                    $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
459
                    
0 ignored issues
show
introduced by
Please trim any trailing whitespace
Loading history...
Coding Style introduced by
Blank line found at end of control structure
Loading history...
460
                }
461
            }
462
463
            // Compose search/replace patterns
464
            $searchPatterns[] = sprintf($fieldSearchPattern, $sqlTableAliasForFieldAlias, $columnName);
465
            $replacements[] = $fieldAlias;
466
        }
467
468
        foreach($orderByClause->orderByItems as $orderByItem) {
469
            // Walk order by item to get string representation of it
470
            $orderByItemString = $this->walkOrderByItem($orderByItem);
471
472
            // Replace path expressions in the order by clause with their column alias
473
            $orderByItemString = preg_replace($searchPatterns, $replacements, $orderByItemString);
474
475
            $orderByItems[] = $orderByItemString;
476
        }
477
478
        return $orderByItems;
479
    }
480
481
    /**
482
     * getter for $orderByPathExpressions
483
     *
484
     * @return array
485
     */
486
    public function getOrderByPathExpressions()
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
487
    {
488
        return $this->orderByPathExpressions;
489
    }
490
491
    /**
492
     * @param SelectStatement $AST
493
     *
494
     * @return string
495
     *
496
     * @throws \Doctrine\ORM\OptimisticLockException
497
     * @throws \Doctrine\ORM\Query\QueryException
498
     */
499
    private function getInnerSQL(SelectStatement $AST)
500
    {
501
        // Set every select expression as visible(hidden = false) to
502
        // make $AST have scalar mappings properly - this is relevant for referencing selected
503
        // fields from outside the subquery, for example in the ORDER BY segment
504
        $hiddens = array();
505
506
        foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
507
            $hiddens[$idx] = $expr->hiddenAliasResultVariable;
508
            $expr->hiddenAliasResultVariable = false;
509
        }
510
511
        $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...
512
513
        // Restore hiddens
514
        foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
515
            $expr->hiddenAliasResultVariable = $hiddens[$idx];
516
        }
517
518
        return $innerSql;
519
    }
520
521
    /**
522
     * @param SelectStatement $AST
523
     *
524
     * @return array
525
     */
526
    private function getSQLIdentifier(SelectStatement $AST)
527
    {
528
        // Find out the SQL alias of the identifier column of the root entity.
529
        // It may be possible to make this work with multiple root entities but that
530
        // would probably require issuing multiple queries or doing a UNION SELECT.
531
        // So for now, it's not supported.
532
533
        // Get the root entity and alias from the AST fromClause.
534
        $from = $AST->fromClause->identificationVariableDeclarations;
535
        if (count($from) !== 1) {
536
            throw new \RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
537
        }
538
539
        $fromRoot       = reset($from);
540
        $rootAlias      = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
541
        $rootClass      = $this->queryComponents[$rootAlias]['metadata'];
542
        $rootIdentifier = $rootClass->identifier;
543
544
        // For every identifier, find out the SQL alias by combing through the ResultSetMapping
545
        $sqlIdentifier = array();
546 View Code Duplication
        foreach ($rootIdentifier as $property) {
547
            if (isset($rootClass->fieldMappings[$property])) {
548
                foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) {
549
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
550
                        $sqlIdentifier[$property] = $alias;
551
                    }
552
                }
553
            }
554
555
            if (isset($rootClass->associationMappings[$property])) {
556
                $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name'];
557
558
                foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) {
559
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
560
                        $sqlIdentifier[$property] = $alias;
561
                    }
562
                }
563
            }
564
        }
565
566
        if (count($sqlIdentifier) === 0) {
567
            throw new \RuntimeException('The Paginator does not support Queries which only yield ScalarResults.');
568
        }
569
570 View Code Duplication
        if (count($rootIdentifier) != count($sqlIdentifier)) {
571
            throw new \RuntimeException(sprintf(
572
                'Not all identifier properties can be found in the ResultSetMapping: %s',
573
                implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier)))
574
            ));
575
        }
576
577
        return $sqlIdentifier;
578
    }
579
580
    /**
581
     * {@inheritdoc}
582
     */
583
    public function walkPathExpression($pathExpr)
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
584
    {
585
        if (!$this->inSubSelect && !$this->platformSupportsRowNumber() && !in_array($pathExpr, $this->orderByPathExpressions)) {
586
            $this->orderByPathExpressions[] = $pathExpr;
587
        }
588
589
        return parent::walkPathExpression($pathExpr);
590
    }
591
592
    /**
593
     * {@inheritdoc}
594
     */
595
    public function walkSubSelect($subselect)
596
    {
597
        $this->inSubSelect = true;
598
599
        $sql = parent::walkSubselect($subselect);
600
601
        $this->inSubSelect = false;
602
603
        return $sql;
604
    }
605
}
606