Failed Conditions
Pull Request — 2.6 (#7882)
by
unknown
06:45
created

LimitSubqueryWalker::walkSelectStatement()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 52
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 8.0017

Importance

Changes 0
Metric Value
eloc 32
c 0
b 0
f 0
dl 0
loc 52
ccs 32
cts 33
cp 0.9697
rs 8.1635
cc 8
nc 7
nop 1
crap 8.0017

How to fix   Long Method   

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