Completed
Pull Request — master (#1922)
by chihiro
109:34
created

CountOutputWalker::walkSelectStatement()   C

Complexity

Conditions 11
Paths 38

Size

Total Lines 51
Code Lines 30

Duplication

Lines 10
Ratio 19.61 %

Code Coverage

Tests 21
CRAP Score 13.5412

Importance

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