Failed Conditions
Push — master ( b79b26...e149f8 )
by Guilherme
64:11
created

LimitSubqueryWalker::walkSelectStatement()   C

Complexity

Conditions 8
Paths 3

Size

Total Lines 52
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 32
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 30
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 52
rs 6.8493
ccs 32
cts 32
cp 1
crap 8

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 30
    public function walkSelectStatement(SelectStatement $AST)
65
    {
66 30
        $queryComponents = $this->_getQueryComponents();
67
        // Get the root entity and alias from the AST fromClause
68 30
        $from      = $AST->fromClause->identificationVariableDeclarations;
69 30
        $fromRoot  = reset($from);
70 30
        $rootAlias = $fromRoot->rangeVariableDeclaration->aliasIdentificationVariable;
71 30
        $rootClass = $queryComponents[$rootAlias]['metadata'];
72 30
73
        $this->validate($AST);
74 30
        $identifier = $rootClass->getSingleIdentifierFieldName();
75
76 28
        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 28
        }
79 2
80 28
        $this->_getQuery()->setHint(
81
            self::IDENTIFIER_TYPE,
82
            Type::getType($rootClass->fieldMappings[$identifier]['type'])
83
        );
84 28
85
        $pathExpression = new PathExpression(
86 28
            PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION,
87 1
            $rootAlias,
88
            $identifier
89
        );
90 27
91 27
        $pathExpression->type = PathExpression::TYPE_STATE_FIELD;
92 27
93
        $AST->selectClause->selectExpressions = array(new SelectExpression($pathExpression, '_dctrn_id'));
94
95 27
        if (isset($AST->orderByClause)) {
96 27
            foreach ($AST->orderByClause->orderByItems as $item) {
97 27
                if ( ! $item->expression instanceof PathExpression) {
98 27
                    if(is_string($item->expression) && isset($queryComponents[$item->expression])) {
99
                        $qComp = $queryComponents[$item->expression];
100
                        if (isset($qComp['resultVariable'])) {
101 27
                            $AST->selectClause->selectExpressions[] = new SelectExpression($qComp['resultVariable'], $item->expression);
102
                        }
103 27
                    }
104
                } else {
105 27
106
                    $AST->selectClause->selectExpressions[] = new SelectExpression(
107 27
                        $this->createSelectExpressionItem($item->expression),
108 21
                        '_dctrn_ord' . $this->_aliasCounter++
109 21
                    );
110 1
                }
111
112
            }
113 20
        }
114 20
115 20
        $AST->selectClause->isDistinct = true;
116
    }
117
118
    /**
119
     * Validate the AST to ensure that this walker is able to properly manipulate it.
120 27
     *
121 27
     * @param SelectStatement $AST
122
     */
123
    private function validate(SelectStatement $AST)
124
    {
125
        // Prevent LimitSubqueryWalker from being used with queries that include
126
        // a limit, a fetched to-many join, and an order by condition that
127
        // references a column from the fetch joined table.
128 30
        $queryComponents = $this->getQueryComponents();
129
        $query           = $this->_getQuery();
130
        $from            = $AST->fromClause->identificationVariableDeclarations;
131
        $fromRoot        = reset($from);
132
133 30
        if ($query instanceof Query
134 30
            && null !== $query->getMaxResults()
135 30
            && $AST->orderByClause
136 30
            && count($fromRoot->joins)) {
137
            // Check each orderby item.
138 30
            // TODO: check complex orderby items too...
139 30
            foreach ($AST->orderByClause->orderByItems as $orderByItem) {
140 30
                $expression = $orderByItem->expression;
141 30
                if ($orderByItem->expression instanceof PathExpression
142
                    && isset($queryComponents[$expression->identificationVariable])) {
143
                    $queryComponent = $queryComponents[$expression->identificationVariable];
144 10
                    if (isset($queryComponent['parent'])
145 10
                        && $queryComponent['relation']['type'] & ClassMetadataInfo::TO_MANY) {
146 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.");
147 10
                    }
148 10
                }
149 10
            }
150 10
        }
151 10
    }
152
153
    /**
154
     * Retrieve either an IdentityFunction (IDENTITY(u.assoc)) or a state field (u.name).
155
     *
156 28
     * @param \Doctrine\ORM\Query\AST\PathExpression $pathExpression
157
     *
158
     * @return \Doctrine\ORM\Query\AST\Functions\IdentityFunction
159
     */
160
    private function createSelectExpressionItem(PathExpression $pathExpression)
161
    {
162
        if ($pathExpression->type === PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
163
            $identity = new IdentityFunction('identity');
164
165 20
            $identity->pathExpression = clone $pathExpression;
166
167 20
            return $identity;
168 1
        }
169
170 1
        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...
171
    }
172
}
173