Failed Conditions
Pull Request — master (#6743)
by Grégoire
18:17 queued 12:33
created

CustomTreeWalker::walkSelectStatement()   C

Complexity

Conditions 8
Paths 42

Size

Total Lines 70
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 37
nc 42
nop 1
dl 0
loc 70
rs 6.4909
c 0
b 0
f 0

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
declare(strict_types=1);
4
5
namespace Doctrine\Tests\ORM\Functional;
6
7
use Doctrine\ORM\Query;
8
use Doctrine\ORM\Query\QueryException;
9
use Doctrine\Tests\Models\CMS\CmsAddress;
10
use Doctrine\Tests\Models\CMS\CmsUser;
11
use Doctrine\Tests\OrmTestCase;
12
13
/**
14
 * Test case for custom AST walking and modification.
15
 *
16
 * @author      Roman Borschel <[email protected]>
17
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
18
 * @link        http://www.doctrine-project.org
19
 * @since       2.0
20
 */
21
class CustomTreeWalkersTest extends OrmTestCase
22
{
23
    private $em;
24
25
    protected function setUp()
26
    {
27
        $this->em = $this->getTestEntityManager();
28
    }
29
30
    public function generateSql($dqlToBeTested, $treeWalkers, $outputWalker)
31
    {
32
        $query = $this->em->createQuery($dqlToBeTested);
33
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $treeWalkers)
34
            ->useQueryCache(false);
35
36
        if ($outputWalker) {
37
            $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, $outputWalker);
38
        }
39
40
        return $query->getSql();
41
    }
42
43
    public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, $treeWalkers = [], $outputWalker = null)
44
    {
45
        try {
46
            $sqlGenerated = $this->generateSql($dqlToBeTested, $treeWalkers, $outputWalker);
47
        } catch (\Exception $e) {
48
            $this->fail($e->getMessage() . ' at "' . $e->getFile() . '" on line ' . $e->getLine());
49
        }
50
51
        self::assertEquals($sqlToBeConfirmed, $sqlGenerated);
52
    }
53
54
    public function testSupportsQueriesWithoutWhere()
55
    {
56
        $this->assertSqlGeneration(
57
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
58
            'SELECT t0."id" AS c0, t0."status" AS c1, t0."username" AS c2, t0."name" AS c3, t0."email_id" AS c4 FROM "cms_users" t0 WHERE t0."id" = 1',
59
            [CustomTreeWalker::class]
60
        );
61
    }
62
63
    public function testSupportsQueriesWithMultipleConditionalExpressions()
64
    {
65
        $this->assertSqlGeneration(
66
            'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name or u.name = :otherName',
67
            'SELECT t0."id" AS c0, t0."status" AS c1, t0."username" AS c2, t0."name" AS c3, t0."email_id" AS c4 FROM "cms_users" t0 WHERE (t0."name" = ? OR t0."name" = ?) AND t0."id" = 1',
68
            [CustomTreeWalker::class]
69
        );
70
    }
71
72
    public function testSupportsQueriesWithSimpleConditionalExpression()
73
    {
74
        $this->assertSqlGeneration(
75
            'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name',
76
            'SELECT t0."id" AS c0, t0."status" AS c1, t0."username" AS c2, t0."name" AS c3, t0."email_id" AS c4 FROM "cms_users" t0 WHERE t0."name" = ? AND t0."id" = 1',
77
            [CustomTreeWalker::class]
78
        );
79
    }
80
81
    public function testSetUnknownQueryComponentThrowsException()
82
    {
83
        $this->expectException(QueryException::class);
84
        $this->expectExceptionMessage("Invalid query component given for DQL alias 'x', requires 'metadata', 'parent', 'relation', 'map', 'nestingLevel' and 'token' keys.");
85
86
        $this->generateSql(
87
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
88
            [],
89
            AddUnknownQueryComponentWalker::class
90
        );
91
    }
92
93
    public function testSupportsSeveralHintsQueries()
94
    {
95
        $this->assertSqlGeneration(
96
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
97
            'SELECT t0."id" AS c0, t0."status" AS c1, t0."username" AS c2, t0."name" AS c3, t1."id" AS c4, t1."country" AS c5, t1."zip" AS c6, t1."city" AS c7, t0."email_id" AS c8, t1."user_id" AS c9 FROM "cms_users" t0 LEFT JOIN "cms_addresses" t1 ON t0."id" = t1."user_id" WHERE t0."id" = 1',
98
            [CustomTreeWalkerJoin::class, CustomTreeWalker::class]
99
        );
100
    }
101
}
102
103
class AddUnknownQueryComponentWalker extends Query\SqlWalker
104
{
105
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
106
    {
107
        parent::walkSelectStatement($selectStatement);
108
109
        $this->setQueryComponent('x', []);
110
    }
111
}
112
113
class CustomTreeWalker extends Query\TreeWalkerAdapter
114
{
115
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
116
    {
117
        // Get the DQL aliases of all the classes we want to modify
118
        $dqlAliases = [];
119
120
        foreach ($this->getQueryComponents() as $dqlAlias => $comp) {
121
            // Hard-coded check just for demonstration: We want to modify the query if
122
            // it involves the CmsUser class.
123
            if ($comp['metadata']->getClassName() == CmsUser::class) {
124
                $dqlAliases[] = $dqlAlias;
125
            }
126
        }
127
128
        // Create our conditions for all involved classes
129
        $factors = [];
130
        foreach ($dqlAliases as $alias) {
131
            $pathExpr = new Query\AST\PathExpression(Query\AST\PathExpression::TYPE_STATE_FIELD, $alias, 'id');
132
            $pathExpr->type = Query\AST\PathExpression::TYPE_STATE_FIELD;
133
            $comparisonExpr = new Query\AST\ComparisonExpression($pathExpr, '=', 1);
134
135
            $condPrimary = new Query\AST\ConditionalPrimary;
136
            $condPrimary->simpleConditionalExpression = $comparisonExpr;
137
138
            $factor = new Query\AST\ConditionalFactor($condPrimary);
139
            $factors[] = $factor;
140
        }
141
142
        if (($whereClause = $selectStatement->whereClause) !== null) {
143
            // There is already a WHERE clause, so append the conditions
144
            $condExpr = $whereClause->conditionalExpression;
145
146
            // Since Phase 1 AST optimizations were included, we need to re-add the ConditionalExpression
147
            if ( ! ($condExpr instanceof Query\AST\ConditionalExpression)) {
148
                $condExpr = new Query\AST\ConditionalExpression([$condExpr]);
149
150
                $whereClause->conditionalExpression = $condExpr;
151
            }
152
153
            $existingTerms = $whereClause->conditionalExpression->conditionalTerms;
154
155
            if (count($existingTerms) > 1) {
156
                // More than one term, so we need to wrap all these terms in a single root term
157
                // i.e: "WHERE u.name = :foo or u.other = :bar" => "WHERE (u.name = :foo or u.other = :bar) AND <our condition>"
158
159
                $primary = new Query\AST\ConditionalPrimary;
160
                $primary->conditionalExpression = new Query\AST\ConditionalExpression($existingTerms);
161
                $existingFactor = new Query\AST\ConditionalFactor($primary);
162
                $term = new Query\AST\ConditionalTerm(array_merge([$existingFactor], $factors));
163
164
                $selectStatement->whereClause->conditionalExpression->conditionalTerms = [$term];
165
            } else {
166
                // Just one term so we can simply append our factors to that term
167
                $singleTerm = $selectStatement->whereClause->conditionalExpression->conditionalTerms[0];
168
169
                // Since Phase 1 AST optimizations were included, we need to re-add the ConditionalExpression
170
                if ( ! ($singleTerm instanceof Query\AST\ConditionalTerm)) {
171
                    $singleTerm = new Query\AST\ConditionalTerm([$singleTerm]);
172
173
                    $selectStatement->whereClause->conditionalExpression->conditionalTerms[0] = $singleTerm;
174
                }
175
176
                $singleTerm->conditionalFactors = array_merge($singleTerm->conditionalFactors, $factors);
177
                $selectStatement->whereClause->conditionalExpression->conditionalTerms = [$singleTerm];
178
            }
179
        } else {
180
            // Create a new WHERE clause with our factors
181
            $term = new Query\AST\ConditionalTerm($factors);
182
            $condExpr = new Query\AST\ConditionalExpression([$term]);
183
            $whereClause = new Query\AST\WhereClause($condExpr);
184
            $selectStatement->whereClause = $whereClause;
185
        }
186
    }
187
}
188
189
class CustomTreeWalkerJoin extends Query\TreeWalkerAdapter
190
{
191
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
192
    {
193
        foreach ($selectStatement->fromClause->identificationVariableDeclarations as $identificationVariableDeclaration) {
194
            $rangeVariableDecl = $identificationVariableDeclaration->rangeVariableDeclaration;
195
196
            if ($rangeVariableDecl->abstractSchemaName !== CmsUser::class) {
197
                continue;
198
            }
199
200
            $this->modifySelectStatement($selectStatement, $identificationVariableDeclaration);
201
        }
202
    }
203
204
    private function modifySelectStatement(Query\AST\SelectStatement $selectStatement, $identificationVariableDecl)
205
    {
206
        $rangeVariableDecl       = $identificationVariableDecl->rangeVariableDeclaration;
207
        $joinAssocPathExpression = new Query\AST\JoinAssociationPathExpression($rangeVariableDecl->aliasIdentificationVariable, 'address');
208
        $joinAssocDeclaration    = new Query\AST\JoinAssociationDeclaration($joinAssocPathExpression, $rangeVariableDecl->aliasIdentificationVariable . 'a', null);
209
        $join                    = new Query\AST\Join(Query\AST\Join::JOIN_TYPE_LEFT, $joinAssocDeclaration);
210
        $selectExpression        = new Query\AST\SelectExpression($rangeVariableDecl->aliasIdentificationVariable . 'a', null, false);
211
212
        $identificationVariableDecl->joins[]                = $join;
213
        $selectStatement->selectClause->selectExpressions[] = $selectExpression;
214
215
        $entityManager   = $this->getQuery()->getEntityManager();
216
        $userMetadata    = $entityManager->getClassMetadata(CmsUser::class);
217
        $addressMetadata = $entityManager->getClassMetadata(CmsAddress::class);
218
219
        $this->setQueryComponent($rangeVariableDecl->aliasIdentificationVariable . 'a',
220
            [
221
                'metadata'     => $addressMetadata,
222
                'parent'       => $rangeVariableDecl->aliasIdentificationVariable,
223
                'relation'     => $userMetadata->getProperty('address'),
224
                'map'          => null,
225
                'nestingLevel' => 0,
226
                'token'        => null,
227
            ]
228
        );
229
    }
230
}
231