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

LimitSubqueryWalker::validate()   B

Complexity

Conditions 10
Paths 5

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 10

Importance

Changes 0
Metric Value
cc 10
eloc 16
nc 5
nop 1
dl 0
loc 24
rs 7.6666
c 0
b 0
f 0
ccs 17
cts 17
cp 1
crap 10

How to fix   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), '_dctrn_ord' . $this->_aliasCounter++
119
                    );
120 21
                    $hasPathExpression = true;
121
                }
122
            }
123
124 25
            if ($hasPathExpression) {
125 21
                continue;
126
            }
127
128 4
            if (is_string($item->expression) && isset($queryComponents[$item->expression])) {
129 3
                $qComp = $queryComponents[$item->expression];
130
131 3
                if (isset($qComp['resultVariable'])) {
132 3
                    $AST->selectClause->selectExpressions[] = new SelectExpression(
133 3
                        $qComp['resultVariable'],
134 4
                        $item->expression
135
                    );
136
                }
137
            }
138
        }
139 25
    }
140
141
    /**
142
     * Validate the AST to ensure that this walker is able to properly manipulate it.
143
     *
144
     * @param SelectStatement $AST
145
     */
146 37
    private function validate(SelectStatement $AST)
147
    {
148
        // Prevent LimitSubqueryWalker from being used with queries that include
149
        // a limit, a fetched to-many join, and an order by condition that
150
        // references a column from the fetch joined table.
151 37
        $queryComponents = $this->getQueryComponents();
152 37
        $query           = $this->_getQuery();
153 37
        $from            = $AST->fromClause->identificationVariableDeclarations;
154 37
        $fromRoot        = reset($from);
155
156 37
        if ($query instanceof Query
157 37
            && null !== $query->getMaxResults()
158 37
            && $AST->orderByClause
159 37
            && count($fromRoot->joins)) {
160
            // Check each orderby item.
161
            // TODO: check complex orderby items too...
162 10
            foreach ($AST->orderByClause->orderByItems as $orderByItem) {
163 10
                $expression = $orderByItem->expression;
164 10
                if ($orderByItem->expression instanceof PathExpression
165 10
                    && isset($queryComponents[$expression->identificationVariable])) {
166 10
                    $queryComponent = $queryComponents[$expression->identificationVariable];
167 10
                    if (isset($queryComponent['parent'])
168 10
                        && $queryComponent['relation']['type'] & ClassMetadataInfo::TO_MANY) {
169 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.");
170
                    }
171
                }
172
            }
173
        }
174 35
    }
175
176
    /**
177
     * Retrieve either an IdentityFunction (IDENTITY(u.assoc)) or a state field (u.name).
178
     *
179
     * @param \Doctrine\ORM\Query\AST\PathExpression $pathExpression
180
     *
181
     * @return \Doctrine\ORM\Query\AST\Functions\IdentityFunction
182
     */
183 21
    private function createSelectExpressionItem(PathExpression $pathExpression)
184
    {
185 21
        if ($pathExpression->type === PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
186 1
            $identity = new IdentityFunction('identity');
187
188 1
            $identity->pathExpression = clone $pathExpression;
189
190 1
            return $identity;
191
        }
192
193 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...
194
    }
195
}
196