Failed Conditions
Pull Request — 2.6 (#7803)
by
unknown
06:17
created

LimitSubqueryWalker::walkSelectStatement()   C

Complexity

Conditions 12
Paths 39

Size

Total Lines 72
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 45
CRAP Score 12

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 46
c 1
b 0
f 0
nc 39
nop 1
dl 0
loc 72
ccs 45
cts 45
cp 1
crap 12
rs 6.9666

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
 * 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\DBAL\Types\Type;
23
use Doctrine\ORM\Mapping\ClassMetadataInfo;
24
use Doctrine\ORM\Query;
25
use Doctrine\ORM\Query\AST\Functions\IdentityFunction;
26
use Doctrine\ORM\Query\AST\PathExpression;
27
use Doctrine\ORM\Query\AST\SelectExpression;
28
use Doctrine\ORM\Query\AST\SelectStatement;
29
use Doctrine\ORM\Query\AST\SimpleArithmeticExpression;
30
use Doctrine\ORM\Query\TreeWalkerAdapter;
31
32
/**
33
 * Replaces the selectClause of the AST with a SELECT DISTINCT root.id equivalent.
34
 *
35
 * @category    DoctrineExtensions
36
 * @package     DoctrineExtensions\Paginate
37
 * @author      David Abdemoulaie <[email protected]>
38
 * @copyright   Copyright (c) 2010 David Abdemoulaie (http://hobodave.com/)
39
 * @license     http://hobodave.com/license.txt New BSD License
40
 */
41
class LimitSubqueryWalker extends TreeWalkerAdapter
42
{
43
    /**
44
     * ID type hint.
45
     */
46
    const IDENTIFIER_TYPE = 'doctrine_paginator.id.type';
47
48
    /**
49
     * Counter for generating unique order column aliases.
50
     *
51
     * @var int
52
     */
53
    private $_aliasCounter = 0;
54
55
    /**
56
     * Walks down a SelectStatement AST node, modifying it to retrieve DISTINCT ids
57
     * of the root Entity.
58
     *
59
     * @param SelectStatement $AST
60
     *
61
     * @return void
62
     *
63
     * @throws \RuntimeException
64
     */
65 37
    public function walkSelectStatement(SelectStatement $AST)
66
    {
67 37
        $queryComponents = $this->_getQueryComponents();
68
        // Get the root entity and alias from the AST fromClause
69 37
        $from      = $AST->fromClause->identificationVariableDeclarations;
70 37
        $fromRoot  = reset($from);
71 37
        $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
72 37
        $rootClass = $queryComponents[$rootAlias]['metadata'];
73
74 37
        $this->validate($AST);
75 35
        $identifier = $rootClass->getSingleIdentifierFieldName();
76
77 35
        if (isset($rootClass->associationMappings[$identifier])) {
78 1
            throw new \RuntimeException("Paginating an entity with foreign key as identifier only works when using the Output Walkers. Call Paginator#setUseOutputWalkers(true) before iterating the paginator.");
79
        }
80
81 34
        $this->_getQuery()->setHint(
82 34
            self::IDENTIFIER_TYPE,
83 34
            Type::getType($rootClass->fieldMappings[$identifier]['type'])
84
        );
85
86 34
        $pathExpression = new PathExpression(
87 34
            PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
88 34
            $rootAlias,
89 34
            $identifier
90
        );
91
92 34
        $pathExpression->type = PathExpression::TYPE_STATE_FIELD;
93
94 34
        $AST->selectClause->selectExpressions = [new SelectExpression($pathExpression, '_dctrn_id')];
95 34
        $AST->selectClause->isDistinct        = true;
96
97 34
        if ( ! isset($AST->orderByClause)) {
98 9
            return;
99
        }
100
101 25
        foreach ($AST->orderByClause->orderByItems as $item) {
102
            switch (true) {
103 25
                case $item->expression instanceof PathExpression:
104 20
                    $expressions = [$item->expression];
105 20
                    break;
106 5
                case $item->expression instanceof SimpleArithmeticExpression:
107 1
                    $expressions = $item->expression->arithmeticTerms;
108 1
                    break;
109
                default:
110 4
                    $expressions = [];
111
            }
112
113 25
            $hasPathExpression = false;
114
115 25
            foreach ($expressions as $expression) {
116 21
                if ($expression instanceof PathExpression) {
117 21
                    $AST->selectClause->selectExpressions[] = new SelectExpression(
118 21
                        $this->createSelectExpressionItem($expression),
119 21
                        '_dctrn_ord' . $this->_aliasCounter++
120
                    );
121
122 21
                    $hasPathExpression = true;
123
                }
124
            }
125
126 25
            if ($hasPathExpression) {
127 21
                continue;
128
            }
129
130 4
            if (is_string($item->expression) && isset($queryComponents[$item->expression])) {
131 3
                $qComp = $queryComponents[$item->expression];
132
133 3
                if (isset($qComp['resultVariable'])) {
134 3
                    $AST->selectClause->selectExpressions[] = new SelectExpression(
135 3
                        $qComp['resultVariable'],
136 4
                        $item->expression
137
                    );
138
                }
139
            }
140
        }
141 25
    }
142
143
    /**
144
     * Validate the AST to ensure that this walker is able to properly manipulate it.
145
     *
146
     * @param SelectStatement $AST
147
     */
148 37
    private function validate(SelectStatement $AST)
149
    {
150
        // Prevent LimitSubqueryWalker from being used with queries that include
151
        // a limit, a fetched to-many join, and an order by condition that
152
        // references a column from the fetch joined table.
153 37
        $queryComponents = $this->getQueryComponents();
154 37
        $query           = $this->_getQuery();
155 37
        $from            = $AST->fromClause->identificationVariableDeclarations;
156 37
        $fromRoot        = reset($from);
157
158 37
        if ($query instanceof Query
159 37
            && null !== $query->getMaxResults()
160 37
            && $AST->orderByClause
161 37
            && count($fromRoot->joins)) {
162
            // Check each orderby item.
163
            // TODO: check complex orderby items too...
164 10
            foreach ($AST->orderByClause->orderByItems as $orderByItem) {
165 10
                $expression = $orderByItem->expression;
166 10
                if ($orderByItem->expression instanceof PathExpression
167 10
                    && isset($queryComponents[$expression->identificationVariable])) {
168 10
                    $queryComponent = $queryComponents[$expression->identificationVariable];
169 10
                    if (isset($queryComponent['parent'])
170 10
                        && $queryComponent['relation']['type'] & ClassMetadataInfo::TO_MANY) {
171 10
                        throw new \RuntimeException("Cannot select distinct identifiers from query with LIMIT and ORDER BY on a column from a fetch joined to-many association. Use output walkers.");
172
                    }
173
                }
174
            }
175
        }
176 35
    }
177
178
    /**
179
     * Retrieve either an IdentityFunction (IDENTITY(u.assoc)) or a state field (u.name).
180
     *
181
     * @param \Doctrine\ORM\Query\AST\PathExpression $pathExpression
182
     *
183
     * @return \Doctrine\ORM\Query\AST\Functions\IdentityFunction
184
     */
185 21
    private function createSelectExpressionItem(PathExpression $pathExpression)
186
    {
187 21
        if ($pathExpression->type === PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
188 1
            $identity = new IdentityFunction('identity');
189
190 1
            $identity->pathExpression = clone $pathExpression;
191
192 1
            return $identity;
193
        }
194
195 20
        return clone $pathExpression;
0 ignored issues
show
Bug Best Practice introduced by
The expression return clone $pathExpression returns the type Doctrine\ORM\Query\AST\PathExpression which is incompatible with the documented return type Doctrine\ORM\Query\AST\Functions\IdentityFunction.
Loading history...
196
    }
197
}
198