Failed Conditions
Pull Request — master (#1922)
by chihiro
118:02
created

CountOutputWalker::walkSelectStatement()   C

Complexity

Conditions 11
Paths 38

Size

Total Lines 60
Code Lines 30

Duplication

Lines 25
Ratio 41.67 %

Code Coverage

Tests 21
CRAP Score 13.5412

Importance

Changes 0
Metric Value
cc 11
eloc 30
nc 38
nop 1
dl 25
loc 60
ccs 21
cts 29
cp 0.7241
crap 13.5412
rs 6.2926
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\ORM\Query\SqlWalker;
17
use Doctrine\ORM\Query\AST\SelectStatement;
18
19
/**
20
 * Wraps the query in order to accurately count the root objects.
21
 *
22
 * Given a DQL like `SELECT u FROM User u` it will generate an SQL query like:
23
 * SELECT COUNT(*) (SELECT DISTINCT <id> FROM (<original SQL>))
24
 *
25
 * Works with composite keys but cannot deal with queries that have multiple
26
 * root entities (e.g. `SELECT f, b from Foo, Bar`)
27
 *
28
 * @author Sander Marechal <[email protected]>
29
 */
30
class CountOutputWalker extends SqlWalker
31
{
32
    /**
33
     * @var \Doctrine\DBAL\Platforms\AbstractPlatform
34
     */
35
    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...
36
37
    /**
38
     * @var \Doctrine\ORM\Query\ResultSetMapping
39
     */
40
    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...
41
42
    /**
43
     * @var array
44
     */
45
    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...
46
47
    /**
48
     * Constructor.
49
     *
50
     * Stores various parameters that are otherwise unavailable
51
     * because Doctrine\ORM\Query\SqlWalker keeps everything private without
52
     * accessors.
53
     *
54
     * @param \Doctrine\ORM\Query              $query
55
     * @param \Doctrine\ORM\Query\ParserResult $parserResult
56
     * @param array                            $queryComponents
57
     */
58 10
    public function __construct($query, $parserResult, array $queryComponents)
59
    {
60 10
        $this->platform = $query->getEntityManager()->getConnection()->getDatabasePlatform();
61 10
        $this->rsm = $parserResult->getResultSetMapping();
62 10
        $this->queryComponents = $queryComponents;
63
64 10
        parent::__construct($query, $parserResult, $queryComponents);
65
    }
66
67
    /**
68
     * Walks down a SelectStatement AST node, wrapping it in a COUNT (SELECT DISTINCT).
69
     *
70
     * Note that the ORDER BY clause is not removed. Many SQL implementations (e.g. MySQL)
71
     * are able to cache subqueries. By keeping the ORDER BY clause intact, the limitSubQuery
72
     * that will most likely be executed next can be read from the native SQL cache.
73
     *
74
     * @param SelectStatement $AST
75
     *
76
     * @return string
77
     *
78
     * @throws \RuntimeException
79
     */
80 10
    public function walkSelectStatement(SelectStatement $AST)
81
    {
82 10
        if ($this->platform->getName() === "mssql") {
83
            $AST->orderByClause = null;
84
        }
85
86 10
        $sql = parent::walkSelectStatement($AST);
87
88
        // Find out the SQL alias of the identifier column of the root entity
89
        // It may be possible to make this work with multiple root entities but that
90
        // would probably require issuing multiple queries or doing a UNION SELECT
91
        // so for now, It's not supported.
92
93
        // Get the root entity and alias from the AST fromClause
94 10
        $from = $AST->fromClause->identificationVariableDeclarations;
95 10
        if (count($from) > 1) {
96
            throw new \RuntimeException("Cannot count query which selects two FROM components, cannot make distinction");
97
        }
98
99 10
        $fromRoot       = reset($from);
100 10
        $rootAlias      = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
101 10
        $rootClass      = $this->queryComponents[$rootAlias]['metadata'];
102 10
        $rootIdentifier = $rootClass->identifier;
103
104
        // For every identifier, find out the SQL alias by combing through the ResultSetMapping
105 10
        $sqlIdentifier = array();
106 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...
107 10
            if (isset($rootClass->fieldMappings[$property])) {
108 10
                foreach (array_keys($this->rsm->fieldMappings, $property) as $alias) {
109 10
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
110 10
                        $sqlIdentifier[$property] = $alias;
111
                    }
112
                }
113
            }
114
115 10
            if (isset($rootClass->associationMappings[$property])) {
116
                $joinColumn = $rootClass->associationMappings[$property]['joinColumns'][0]['name'];
117
118
                foreach (array_keys($this->rsm->metaMappings, $joinColumn) as $alias) {
119
                    if ($this->rsm->columnOwnerMap[$alias] == $rootAlias) {
120 10
                        $sqlIdentifier[$property] = $alias;
121
                    }
122
                }
123
            }
124
        }
125
126 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...
127
            throw new \RuntimeException(sprintf(
128
                'Not all identifier properties can be found in the ResultSetMapping: %s',
129
                implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier)))
130
            ));
131
        }
132
133
        // Build the counter query
134 10
        return sprintf('SELECT %s AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table',
135 10
            $this->platform->getCountExpression('*'),
136 10
            implode(', ', $sqlIdentifier),
137
            $sql
138
        );
139
    }
140
}
141