Failed Conditions
Pull Request — master (#1922)
by chihiro
49:10 queued 16:45
created

LimitSubqueryOutputWalker::preserveSqlOrdering()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 22
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 4
dl 0
loc 22
ccs 0
cts 9
cp 0
crap 6
rs 9.2
c 0
b 0
f 0
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 10
    public function __construct($query, $parserResult, array $queryComponents)
109
    {
110 10
        $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
111 10
        $this->rsm = $parserResult->getResultSetMapping();
112 10
        $this->queryComponents = $queryComponents;
113
114
        // Reset limit and offset
115 10
        $this->firstResult = $query->getFirstResult();
116 10
        $this->maxResults = $query->getMaxResults();
117 10
        $query->setFirstResult(null)->setMaxResults(null);
118
119 10
        $this->em               = $query->getEntityManager();
120 10
        $this->quoteStrategy    = $this->em->getConfiguration()->getQuoteStrategy();
121
122 10
        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 10
    private function platformSupportsRowNumber()
131
    {
132 10
        return $this->platform instanceof PostgreSqlPlatform
133 10
            || $this->platform instanceof SQLServerPlatform
134 10
            || $this->platform instanceof OraclePlatform
135 10
            || $this->platform instanceof SQLAnywherePlatform
136 10
            || $this->platform instanceof DB2Platform
137
            || (method_exists($this->platform, 'supportsRowNumberFunction')
138 10
                && $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 10
    private function rebuildOrderByForRowNumber(SelectStatement $AST)
148
    {
149 10
        $orderByClause = $AST->orderByClause;
150 10
        $selectAliasToExpressionMap = array();
151
        // Get any aliases that are available for select expressions.
152 10
        foreach ($AST->selectClause->selectExpressions as $selectExpression) {
153 10
            $selectAliasToExpressionMap[$selectExpression->fieldIdentificationVariable] = $selectExpression->expression;
154
        }
155
156
        // Rebuild string orderby expressions to use the select expression they're referencing
157 10
        foreach ($orderByClause->orderByItems as $orderByItem) {
158 10
            if (is_string($orderByItem->expression) && isset($selectAliasToExpressionMap[$orderByItem->expression])) {
159 10
                $orderByItem->expression = $selectAliasToExpressionMap[$orderByItem->expression];
160
            }
161
        }
162 10
        $func = new RowNumberOverFunction('dctrn_rownum');
163 10
        $func->orderByClause = $AST->orderByClause;
164 10
        $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 10
        $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 10
    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 10
        if ($this->platformSupportsRowNumber()) {
182 10
            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 10
    public function walkSelectStatementWithRowNumber(SelectStatement $AST)
198
    {
199 10
        $hasOrderBy = false;
200 10
        $outerOrderBy = ' ORDER BY dctrn_minrownum ASC';
201 10
        $orderGroupBy = '';
202 10
        if ($AST->orderByClause instanceof OrderByClause) {
203 10
            $hasOrderBy = true;
204 10
            $this->rebuildOrderByForRowNumber($AST);
205
        }
206
207 10
        $innerSql = $this->getInnerSQL($AST);
208
209 10
        $sqlIdentifier = $this->getSQLIdentifier($AST);
210
211 10
        if ($hasOrderBy) {
212 10
            $orderGroupBy = ' GROUP BY ' . implode(', ', $sqlIdentifier);
0 ignored issues
show
Coding Style introduced by
Concat operator must not be surrounded by spaces
Loading history...
213 10
            $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 10
        $sql = sprintf(
218 10
            'SELECT DISTINCT %s FROM (%s) dctrn_result',
219 10
            implode(', ', $sqlIdentifier),
220
            $innerSql
221
        );
222
223 10
        if ($hasOrderBy) {
224 10
            $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 10
        $sql = $this->platform->modifyLimitQuery(
229
            $sql,
230 10
            $this->maxResults,
231 10
            $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 10
        foreach ($sqlIdentifier as $property => $alias) {
239 10
            $this->rsm->addScalarResult($alias, $property);
240
        }
241
242 10
        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
        // Build the select distinct statement
380
        $sql = sprintf(
381
            'SELECT DISTINCT %s FROM (%s) dctrn_result ORDER BY %s',
382
            implode(', ', $sqlIdentifier),
383
            $innerSql,
384
            implode(', ', $orderBy)
385
        );
386
387
        return $sql;
388
    }
389
390
    /**
391
     * Generates a new order by clause that works in the scope of a select query wrapping the original
392
     *
393
     * @param OrderByClause $orderByClause
394
     * @return array
395
     */
396
    private function rebuildOrderByClauseForOuterScope(OrderByClause $orderByClause)
397
    {
398
        $dqlAliasToSqlTableAliasMap
399
            = $searchPatterns
400
            = $replacements
401
            = $dqlAliasToClassMap
402
            = $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...
403
            = $orderByItems
404
            = array();
405
406
        // Generate DQL alias -> SQL table alias mapping
407
        foreach(array_keys($this->rsm->aliasMap) as $dqlAlias) {
408
            $dqlAliasToClassMap[$dqlAlias] = $class = $this->queryComponents[$dqlAlias]['metadata'];
409
            $dqlAliasToSqlTableAliasMap[$dqlAlias] = $this->getSQLTableAlias($class->getTableName(), $dqlAlias);
410
        }
411
412
        // Pattern to find table path expressions in the order by clause
413
        $fieldSearchPattern = '/(?<![a-z0-9_])%s\.%s(?![a-z0-9_])/i';
414
415
        // Generate search patterns for each field's path expression in the order by clause
416
        foreach($this->rsm->fieldMappings as $fieldAlias => $fieldName) {
417
            $dqlAliasForFieldAlias = $this->rsm->columnOwnerMap[$fieldAlias];
418
            $class = $dqlAliasToClassMap[$dqlAliasForFieldAlias];
419
420
            // If the field is from a joined child table, we won't be ordering
421
            // on it.
422
            if (!isset($class->fieldMappings[$fieldName])) {
423
                continue;
424
            }
425
426
            $fieldMapping = $class->fieldMappings[$fieldName];
427
428
            // Get the proper column name as will appear in the select list
429
            $columnName = $this->quoteStrategy->getColumnName(
430
                $fieldName,
431
                $dqlAliasToClassMap[$dqlAliasForFieldAlias],
432
                $this->em->getConnection()->getDatabasePlatform()
433
            );
434
435
            // Get the SQL table alias for the entity and field
436
            $sqlTableAliasForFieldAlias = $dqlAliasToSqlTableAliasMap[$dqlAliasForFieldAlias];
437
            if (isset($fieldMapping['declared']) && $fieldMapping['declared'] !== $class->name) {
438
                // Field was declared in a parent class, so we need to get the proper SQL table alias
439
                // for the joined parent table.
440
                $otherClassMetadata = $this->em->getClassMetadata($fieldMapping['declared']);
441
                if (!$otherClassMetadata->isMappedSuperclass) {
442
                    $sqlTableAliasForFieldAlias = $this->getSQLTableAlias($otherClassMetadata->getTableName(), $dqlAliasForFieldAlias);
443
                    
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...
444
                }
445
            }
446
447
            // Compose search/replace patterns
448
            $searchPatterns[] = sprintf($fieldSearchPattern, $sqlTableAliasForFieldAlias, $columnName);
449
            $replacements[] = $fieldAlias;
450
        }
451
452
        foreach($orderByClause->orderByItems as $orderByItem) {
453
            // Walk order by item to get string representation of it
454
            $orderByItemString = $this->walkOrderByItem($orderByItem);
455
456
            // Replace path expressions in the order by clause with their column alias
457
            $orderByItemString = preg_replace($searchPatterns, $replacements, $orderByItemString);
458
459
            $orderByItems[] = $orderByItemString;
460
        }
461
462
        return $orderByItems;
463
    }
464
465
    /**
466
     * getter for $orderByPathExpressions
467
     *
468
     * @return array
469
     */
470
    public function getOrderByPathExpressions()
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
471
    {
472
        return $this->orderByPathExpressions;
473
    }
474
475
    /**
476
     * @param SelectStatement $AST
477
     *
478
     * @return string
479
     *
480
     * @throws \Doctrine\ORM\OptimisticLockException
481
     * @throws \Doctrine\ORM\Query\QueryException
482
     */
483 10
    private function getInnerSQL(SelectStatement $AST)
484
    {
485
        // Set every select expression as visible(hidden = false) to
486
        // make $AST have scalar mappings properly - this is relevant for referencing selected
487
        // fields from outside the subquery, for example in the ORDER BY segment
488 10
        $hiddens = array();
489
490 10
        foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
491 10
            $hiddens[$idx] = $expr->hiddenAliasResultVariable;
492 10
            $expr->hiddenAliasResultVariable = false;
493
        }
494
495 10
        $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...
496
497
        // Restore hiddens
498 10
        foreach ($AST->selectClause->selectExpressions as $idx => $expr) {
499 10
            $expr->hiddenAliasResultVariable = $hiddens[$idx];
500
        }
501
502 10
        return $innerSql;
503
    }
504
505
    /**
506
     * @param SelectStatement $AST
507
     *
508
     * @return array
509
     */
510 10
    private function getSQLIdentifier(SelectStatement $AST)
511
    {
512
        // Find out the SQL alias of the identifier column of the root entity.
513
        // It may be possible to make this work with multiple root entities but that
514
        // would probably require issuing multiple queries or doing a UNION SELECT.
515
        // So for now, it's not supported.
516
517
        // Get the root entity and alias from the AST fromClause.
518 10
        $from = $AST->fromClause->identificationVariableDeclarations;
519 10
        if (count($from) !== 1) {
520
            throw new \RuntimeException('Cannot count query which selects two FROM components, cannot make distinction');
521
        }
522
523 10
        $fromRoot       = reset($from);
524 10
        $rootAlias      = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
525 10
        $rootClass      = $this->queryComponents[$rootAlias]['metadata'];
526 10
        $rootIdentifier = $rootClass->identifier;
527
528
        // For every identifier, find out the SQL alias by combing through the ResultSetMapping
529 10
        $sqlIdentifier = array();
530 10 View Code Duplication
        foreach ($rootIdentifier as $property) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
531 10
            if (isset($rootClass->fieldMappings[$property])) {
532 10
                foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) {
533 10
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
534 10
                        $sqlIdentifier[$property] = $alias;
535
                    }
536
                }
537
            }
538
539 10
            if (isset($rootClass->associationMappings[$property])) {
540
                $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name'];
541
542
                foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) {
543
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
544 10
                        $sqlIdentifier[$property] = $alias;
545
                    }
546
                }
547
            }
548
        }
549
550 10
        if (count($sqlIdentifier) === 0) {
551
            throw new \RuntimeException('The Paginator does not support Queries which only yield ScalarResults.');
552
        }
553
554 10 View Code Duplication
        if (count($rootIdentifier) != count($sqlIdentifier)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
555
            throw new \RuntimeException(sprintf(
556
                'Not all identifier properties can be found in the ResultSetMapping: %s',
557
                implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier)))
558
            ));
559
        }
560
561 10
        return $sqlIdentifier;
562
    }
563
564
    /**
565
     * {@inheritdoc}
566
     */
567 10
    public function walkPathExpression($pathExpr)
0 ignored issues
show
introduced by
Declare public methods first, then protected ones and finally private ones
Loading history...
568
    {
569 10
        if (!$this->inSubSelect && !$this->platformSupportsRowNumber() && !in_array($pathExpr, $this->orderByPathExpressions)) {
570
            $this->orderByPathExpressions[] = $pathExpr;
571
        }
572
573 10
        return parent::walkPathExpression($pathExpr);
574
    }
575
576
    /**
577
     * {@inheritdoc}
578
     */
579 1
    public function walkSubSelect($subselect)
580
    {
581 1
        $this->inSubSelect = true;
582
583 1
        $sql = parent::walkSubselect($subselect);
584
585 1
        $this->inSubSelect = false;
586
587 1
        return $sql;
588
    }
589
}
590