Failed Conditions
Pull Request — master (#6959)
by Matthew
19:32
created

CountOutputWalker::walkSelectStatement()   C

Complexity

Conditions 12
Paths 28

Size

Total Lines 68
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 14.6255

Importance

Changes 0
Metric Value
cc 12
eloc 37
nc 28
nop 1
dl 0
loc 68
rs 5.7751
c 0
b 0
f 0
ccs 28
cts 38
cp 0.7368
crap 14.6255

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

113
                /** @scrutinizer ignore-call */ 
114
                $joinColumns = $property->getJoinColumns();
Loading history...
114
                $joinColumn  = reset($joinColumns);
115
116
                foreach (array_keys($this->rsm->metaMappings, $joinColumn->getColumnName()) as $alias) {
117
                    if ($this->rsm->columnOwnerMap[$alias] === $rootAlias) {
118 9
                        $sqlIdentifier[$identifier] = $alias;
119
                    }
120
                }
121
            }
122
        }
123
124 9
        if (count($rootIdentifier) !== count($sqlIdentifier)) {
125
            throw new \RuntimeException(sprintf(
126
                'Not all identifier properties can be found in the ResultSetMapping: %s',
127
                implode(', ', array_diff($rootIdentifier, array_keys($sqlIdentifier)))
128
            ));
129
        }
130
131
        // Build the counter query
132 9
        return sprintf(
133 9
            'SELECT %s AS dctrn_count FROM (SELECT DISTINCT %s FROM (%s) dctrn_result) dctrn_table',
134 9
            $this->platform->getCountExpression('*'),
135 9
            implode(', ', $sqlIdentifier),
136 9
            $sql
137
        );
138
    }
139
}
140