Failed Conditions
Pull Request — master (#7046)
by Gabriel
14:57
created

CustomTreeWalkerJoin   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 39
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 10
c 0
b 0
f 0
wmc 4

2 Methods

Rating   Name   Duplication   Size   Complexity  
A walkSelectStatement() 0 10 3
B modifySelectStatement() 0 24 1
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
 * @link        http://www.doctrine-project.org
17
 */
18
class CustomTreeWalkersTest extends OrmTestCase
19
{
20
    private $em;
21
22
    protected function setUp()
23
    {
24
        $this->em = $this->getTestEntityManager();
25
    }
26
27
    public function generateSql($dqlToBeTested, $treeWalkers, $outputWalker)
28
    {
29
        $query = $this->em->createQuery($dqlToBeTested);
30
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $treeWalkers)
31
            ->useQueryCache(false);
32
33
        if ($outputWalker) {
34
            $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, $outputWalker);
35
        }
36
37
        return $query->getSql();
38
    }
39
40
    public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, $treeWalkers = [], $outputWalker = null)
41
    {
42
        try {
43
            $sqlGenerated = $this->generateSql($dqlToBeTested, $treeWalkers, $outputWalker);
44
        } catch (\Exception $e) {
45
            $this->fail($e->getMessage() . ' at "' . $e->getFile() . '" on line ' . $e->getLine());
46
        }
47
48
        self::assertEquals($sqlToBeConfirmed, $sqlGenerated);
49
    }
50
51
    public function testSupportsQueriesWithoutWhere()
52
    {
53
        $this->assertSqlGeneration(
54
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
55
            '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',
56
            [CustomTreeWalker::class]
57
        );
58
    }
59
60
    public function testSupportsQueriesWithMultipleConditionalExpressions()
61
    {
62
        $this->assertSqlGeneration(
63
            'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name or u.name = :otherName',
64
            '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',
65
            [CustomTreeWalker::class]
66
        );
67
    }
68
69
    public function testSupportsQueriesWithSimpleConditionalExpression()
70
    {
71
        $this->assertSqlGeneration(
72
            'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name',
73
            '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',
74
            [CustomTreeWalker::class]
75
        );
76
    }
77
78
    public function testSetUnknownQueryComponentThrowsException()
79
    {
80
        $this->expectException(QueryException::class);
81
        $this->expectExceptionMessage("Invalid query component given for DQL alias 'x', requires 'metadata', 'parent', 'relation', 'map', 'nestingLevel' and 'token' keys.");
82
83
        $this->generateSql(
84
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
85
            [],
86
            AddUnknownQueryComponentWalker::class
87
        );
88
    }
89
90
    public function testSupportsSeveralHintsQueries()
91
    {
92
        $this->assertSqlGeneration(
93
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
94
            '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',
95
            [CustomTreeWalkerJoin::class, CustomTreeWalker::class]
96
        );
97
    }
98
}
99
100
class AddUnknownQueryComponentWalker extends Query\SqlWalker
101
{
102
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
103
    {
104
        parent::walkSelectStatement($selectStatement);
105
106
        $this->setQueryComponent('x', []);
107
    }
108
}
109
110
class CustomTreeWalker extends Query\TreeWalkerAdapter
111
{
112
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
113
    {
114
        // Get the DQL aliases of all the classes we want to modify
115
        $dqlAliases = [];
116
117
        foreach ($this->getQueryComponents() as $dqlAlias => $comp) {
118
            // Hard-coded check just for demonstration: We want to modify the query if
119
            // it involves the CmsUser class.
120
            if ($comp['metadata']->getClassName() === CmsUser::class) {
121
                $dqlAliases[] = $dqlAlias;
122
            }
123
        }
124
125
        // Create our conditions for all involved classes
126
        $factors = [];
127
        foreach ($dqlAliases as $alias) {
128
            $pathExpr       = new Query\AST\PathExpression(Query\AST\PathExpression::TYPE_STATE_FIELD, $alias, 'id');
129
            $pathExpr->type = Query\AST\PathExpression::TYPE_STATE_FIELD;
130
            $comparisonExpr = new Query\AST\ComparisonExpression($pathExpr, '=', 1);
0 ignored issues
show
Bug introduced by
1 of type integer is incompatible with the type Doctrine\ORM\Query\AST\Node expected by parameter $rightExpr of Doctrine\ORM\Query\AST\C...pression::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

130
            $comparisonExpr = new Query\AST\ComparisonExpression($pathExpr, '=', /** @scrutinizer ignore-type */ 1);
Loading history...
131
132
            $condPrimary                              = new Query\AST\ConditionalPrimary;
133
            $condPrimary->simpleConditionalExpression = $comparisonExpr;
134
135
            $factor    = new Query\AST\ConditionalFactor($condPrimary);
136
            $factors[] = $factor;
137
        }
138
139
        if (($whereClause = $selectStatement->whereClause) !== null) {
140
            // There is already a WHERE clause, so append the conditions
141
            $condExpr = $whereClause->conditionalExpression;
142
143
            // Since Phase 1 AST optimizations were included, we need to re-add the ConditionalExpression
144
            if (! ($condExpr instanceof Query\AST\ConditionalExpression)) {
0 ignored issues
show
introduced by
The condition ! $condExpr instanceof D...T\ConditionalExpression can never be true.
Loading history...
145
                $condExpr = new Query\AST\ConditionalExpression([$condExpr]);
146
147
                $whereClause->conditionalExpression = $condExpr;
148
            }
149
150
            $existingTerms = $whereClause->conditionalExpression->conditionalTerms;
151
152
            if (count($existingTerms) > 1) {
153
                // More than one term, so we need to wrap all these terms in a single root term
154
                // i.e: "WHERE u.name = :foo or u.other = :bar" => "WHERE (u.name = :foo or u.other = :bar) AND <our condition>"
155
156
                $primary                        = new Query\AST\ConditionalPrimary;
157
                $primary->conditionalExpression = new Query\AST\ConditionalExpression($existingTerms);
158
                $existingFactor                 = new Query\AST\ConditionalFactor($primary);
159
                $term                           = new Query\AST\ConditionalTerm(array_merge([$existingFactor], $factors));
160
161
                $selectStatement->whereClause->conditionalExpression->conditionalTerms = [$term];
162
            } else {
163
                // Just one term so we can simply append our factors to that term
164
                $singleTerm = $selectStatement->whereClause->conditionalExpression->conditionalTerms[0];
165
166
                // Since Phase 1 AST optimizations were included, we need to re-add the ConditionalExpression
167
                if (! ($singleTerm instanceof Query\AST\ConditionalTerm)) {
0 ignored issues
show
introduced by
The condition ! $singleTerm instanceof...ery\AST\ConditionalTerm can never be true.
Loading history...
168
                    $singleTerm = new Query\AST\ConditionalTerm([$singleTerm]);
169
170
                    $selectStatement->whereClause->conditionalExpression->conditionalTerms[0] = $singleTerm;
171
                }
172
173
                $singleTerm->conditionalFactors                                        = array_merge($singleTerm->conditionalFactors, $factors);
174
                $selectStatement->whereClause->conditionalExpression->conditionalTerms = [$singleTerm];
175
            }
176
        } else {
177
            // Create a new WHERE clause with our factors
178
            $term                         = new Query\AST\ConditionalTerm($factors);
179
            $condExpr                     = new Query\AST\ConditionalExpression([$term]);
180
            $whereClause                  = new Query\AST\WhereClause($condExpr);
181
            $selectStatement->whereClause = $whereClause;
182
        }
183
    }
184
}
185
186
class CustomTreeWalkerJoin extends Query\TreeWalkerAdapter
187
{
188
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
189
    {
190
        foreach ($selectStatement->fromClause->identificationVariableDeclarations as $identificationVariableDeclaration) {
191
            $rangeVariableDecl = $identificationVariableDeclaration->rangeVariableDeclaration;
192
193
            if ($rangeVariableDecl->abstractSchemaName !== CmsUser::class) {
194
                continue;
195
            }
196
197
            $this->modifySelectStatement($selectStatement, $identificationVariableDeclaration);
198
        }
199
    }
200
201
    private function modifySelectStatement(Query\AST\SelectStatement $selectStatement, $identificationVariableDecl)
202
    {
203
        $rangeVariableDecl       = $identificationVariableDecl->rangeVariableDeclaration;
204
        $joinAssocPathExpression = new Query\AST\JoinAssociationPathExpression($rangeVariableDecl->aliasIdentificationVariable, 'address');
205
        $joinAssocDeclaration    = new Query\AST\JoinAssociationDeclaration($joinAssocPathExpression, $rangeVariableDecl->aliasIdentificationVariable . 'a', null);
206
        $join                    = new Query\AST\Join(Query\AST\Join::JOIN_TYPE_LEFT, $joinAssocDeclaration);
207
        $selectExpression        = new Query\AST\SelectExpression($rangeVariableDecl->aliasIdentificationVariable . 'a', null, false);
208
209
        $identificationVariableDecl->joins[]                = $join;
210
        $selectStatement->selectClause->selectExpressions[] = $selectExpression;
211
212
        $entityManager   = $this->getQuery()->getEntityManager();
213
        $userMetadata    = $entityManager->getClassMetadata(CmsUser::class);
214
        $addressMetadata = $entityManager->getClassMetadata(CmsAddress::class);
215
216
        $this->setQueryComponent(
217
            $rangeVariableDecl->aliasIdentificationVariable . 'a',
218
            [
219
                'metadata'     => $addressMetadata,
220
                'parent'       => $rangeVariableDecl->aliasIdentificationVariable,
221
                'relation'     => $userMetadata->getProperty('address'),
0 ignored issues
show
Bug introduced by
The method getProperty() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

221
                'relation'     => $userMetadata->/** @scrutinizer ignore-call */ getProperty('address'),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
222
                'map'          => null,
223
                'nestingLevel' => 0,
224
                'token'        => null,
225
            ]
226
        );
227
    }
228
}
229