Failed Conditions
Pull Request — master (#6485)
by Alessandro
12:30
created

CountOutputWalker   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 119
Duplicated Lines 21.01 %

Coupling/Cohesion

Components 1
Dependencies 9

Test Coverage

Coverage 87.8%

Importance

Changes 0
Metric Value
wmc 13
lcom 1
cbo 9
dl 25
loc 119
ccs 36
cts 41
cp 0.878
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
C walkSelectStatement() 25 68 12

How to fix   Duplicated Code   

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:

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