Completed
Push — master ( 3ca682...b22cda )
by Marco
09:29
created

CustomTreeWalker   A

Complexity

Total Complexity 8

Size/Duplication

Total Lines 75
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 9
Metric Value
wmc 8
lcom 0
cbo 9
dl 0
loc 75
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
C walkSelectStatement() 0 72 8
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 LGPL. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\Tests\ORM\Functional;
21
22
use Doctrine\ORM\Query;
23
24
/**
25
 * Test case for custom AST walking and modification.
26
 *
27
 * @author      Roman Borschel <[email protected]>
28
 * @license     http://www.opensource.org/licenses/lgpl-license.php LGPL
29
 * @link        http://www.doctrine-project.org
30
 * @since       2.0
31
 */
32
class CustomTreeWalkersTest extends \Doctrine\Tests\OrmTestCase
33
{
34
    private $_em;
35
36
    protected function setUp()
37
    {
38
        $this->_em = $this->_getTestEntityManager();
39
    }
40
41
    public function generateSql($dqlToBeTested, $treeWalkers, $outputWalker)
42
    {
43
        $query = $this->_em->createQuery($dqlToBeTested);
44
        $query->setHint(Query::HINT_CUSTOM_TREE_WALKERS, $treeWalkers)
45
            ->useQueryCache(false);
46
47
        if ($outputWalker) {
48
            $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, $outputWalker);
49
        }
50
51
        return $query->getSql();
52
    }
53
54
    public function assertSqlGeneration($dqlToBeTested, $sqlToBeConfirmed, $treeWalkers = array(), $outputWalker = null)
55
    {
56
        try {
57
            $this->assertEquals($sqlToBeConfirmed, $this->generateSql($dqlToBeTested, $treeWalkers, $outputWalker));
58
        } catch (\Exception $e) {
59
            $this->fail($e->getMessage() . ' at "' . $e->getFile() . '" on line ' . $e->getLine());
60
        }
61
    }
62
63
    public function testSupportsQueriesWithoutWhere()
64
    {
65
        $this->assertSqlGeneration(
66
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
67
            "SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, c0_.email_id AS email_id_4 FROM cms_users c0_ WHERE c0_.id = 1",
68
            array('Doctrine\Tests\ORM\Functional\CustomTreeWalker')
69
        );
70
    }
71
72
    public function testSupportsQueriesWithMultipleConditionalExpressions()
73
    {
74
        $this->assertSqlGeneration(
75
            'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name or u.name = :otherName',
76
            "SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, c0_.email_id AS email_id_4 FROM cms_users c0_ WHERE (c0_.name = ? OR c0_.name = ?) AND c0_.id = 1",
77
            array('Doctrine\Tests\ORM\Functional\CustomTreeWalker')
78
        );
79
    }
80
81
    public function testSupportsQueriesWithSimpleConditionalExpression()
82
    {
83
        $this->assertSqlGeneration(
84
            'select u from Doctrine\Tests\Models\CMS\CmsUser u where u.name = :name',
85
            "SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, c0_.email_id AS email_id_4 FROM cms_users c0_ WHERE c0_.name = ? AND c0_.id = 1",
86
            array('Doctrine\Tests\ORM\Functional\CustomTreeWalker')
87
        );
88
    }
89
90
    public function testSetUnknownQueryComponentThrowsException()
91
    {
92
        $this->setExpectedException("Doctrine\ORM\Query\QueryException", "Invalid query component given for DQL alias 'x', requires 'metadata', 'parent', 'relation', 'map', 'nestingLevel' and 'token' keys.");
93
        $this->generateSql(
94
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
95
            array(),
96
            __NAMESPACE__ . '\\AddUnknownQueryComponentWalker'
97
        );
98
    }
99
100
    public function testSupportsSeveralHintsQueries()
101
    {
102
        $this->assertSqlGeneration(
103
            'select u from Doctrine\Tests\Models\CMS\CmsUser u',
104
            "SELECT c0_.id AS id_0, c0_.status AS status_1, c0_.username AS username_2, c0_.name AS name_3, c1_.id AS id_4, c1_.country AS country_5, c1_.zip AS zip_6, c1_.city AS city_7, c0_.email_id AS email_id_8, c1_.user_id AS user_id_9 FROM cms_users c0_ LEFT JOIN cms_addresses c1_ ON c0_.id = c1_.user_id WHERE c0_.id = 1",
105
            array('Doctrine\Tests\ORM\Functional\CustomTreeWalkerJoin', 'Doctrine\Tests\ORM\Functional\CustomTreeWalker')
106
        );
107
    }
108
}
109
110
class AddUnknownQueryComponentWalker extends Query\SqlWalker
111
{
112
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
113
    {
114
        parent::walkSelectStatement($selectStatement);
115
116
        $this->setQueryComponent('x', array());
117
    }
118
}
119
120
class CustomTreeWalker extends Query\TreeWalkerAdapter
121
{
122
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
123
    {
124
        // Get the DQL aliases of all the classes we want to modify
125
        $dqlAliases = array();
126
127
        foreach ($this->_getQueryComponents() as $dqlAlias => $comp) {
128
            // Hard-coded check just for demonstration: We want to modify the query if
129
            // it involves the CmsUser class.
130
            if ($comp['metadata']->name == 'Doctrine\Tests\Models\CMS\CmsUser') {
131
                $dqlAliases[] = $dqlAlias;
132
            }
133
        }
134
135
        // Create our conditions for all involved classes
136
        $factors = array();
137
        foreach ($dqlAliases as $alias) {
138
            $pathExpr = new Query\AST\PathExpression(Query\AST\PathExpression::TYPE_STATE_FIELD, $alias, 'id');
139
            $pathExpr->type = Query\AST\PathExpression::TYPE_STATE_FIELD;
140
            $comparisonExpr = new Query\AST\ComparisonExpression($pathExpr, '=', 1);
0 ignored issues
show
Documentation introduced by
1 is of type integer, but the function expects a object<Doctrine\ORM\Query\AST\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
141
142
            $condPrimary = new Query\AST\ConditionalPrimary;
143
            $condPrimary->simpleConditionalExpression = $comparisonExpr;
144
145
            $factor = new Query\AST\ConditionalFactor($condPrimary);
146
            $factors[] = $factor;
147
        }
148
149
        if (($whereClause = $selectStatement->whereClause) !== null) {
150
            // There is already a WHERE clause, so append the conditions
151
            $condExpr = $whereClause->conditionalExpression;
152
153
            // Since Phase 1 AST optimizations were included, we need to re-add the ConditionalExpression
154
            if ( ! ($condExpr instanceof Query\AST\ConditionalExpression)) {
155
                $condExpr = new Query\AST\ConditionalExpression(array($condExpr));
156
157
                $whereClause->conditionalExpression = $condExpr;
158
            }
159
160
            $existingTerms = $whereClause->conditionalExpression->conditionalTerms;
161
162
            if (count($existingTerms) > 1) {
163
                // More than one term, so we need to wrap all these terms in a single root term
164
                // i.e: "WHERE u.name = :foo or u.other = :bar" => "WHERE (u.name = :foo or u.other = :bar) AND <our condition>"
165
166
                $primary = new Query\AST\ConditionalPrimary;
167
                $primary->conditionalExpression = new Query\AST\ConditionalExpression($existingTerms);
168
                $existingFactor = new Query\AST\ConditionalFactor($primary);
169
                $term = new Query\AST\ConditionalTerm(array_merge(array($existingFactor), $factors));
170
171
                $selectStatement->whereClause->conditionalExpression->conditionalTerms = array($term);
172
            } else {
173
                // Just one term so we can simply append our factors to that term
174
                $singleTerm = $selectStatement->whereClause->conditionalExpression->conditionalTerms[0];
175
176
                // Since Phase 1 AST optimizations were included, we need to re-add the ConditionalExpression
177
                if ( ! ($singleTerm instanceof Query\AST\ConditionalTerm)) {
178
                    $singleTerm = new Query\AST\ConditionalTerm(array($singleTerm));
179
180
                    $selectStatement->whereClause->conditionalExpression->conditionalTerms[0] = $singleTerm;
181
                }
182
183
                $singleTerm->conditionalFactors = array_merge($singleTerm->conditionalFactors, $factors);
184
                $selectStatement->whereClause->conditionalExpression->conditionalTerms = array($singleTerm);
185
            }
186
        } else {
187
            // Create a new WHERE clause with our factors
188
            $term = new Query\AST\ConditionalTerm($factors);
189
            $condExpr = new Query\AST\ConditionalExpression(array($term));
190
            $whereClause = new Query\AST\WhereClause($condExpr);
191
            $selectStatement->whereClause = $whereClause;
192
        }
193
    }
194
}
195
196
class CustomTreeWalkerJoin extends Query\TreeWalkerAdapter
197
{
198
    public function walkSelectStatement(Query\AST\SelectStatement $selectStatement)
199
    {
200
        foreach ($selectStatement->fromClause->identificationVariableDeclarations as $identificationVariableDeclaration) {
201
            $rangeVariableDecl = $identificationVariableDeclaration->rangeVariableDeclaration;
202
            
203
            if ($rangeVariableDecl->abstractSchemaName !== 'Doctrine\Tests\Models\CMS\CmsUser') {
204
                continue;
205
            }
206
            
207
            $this->modifySelectStatement($selectStatement, $identificationVariableDeclaration);
208
        }
209
    }
210
    
211
    private function modifySelectStatement(Query\AST\SelectStatement $selectStatement, $identificationVariableDecl)
212
    {
213
        $rangeVariableDecl       = $identificationVariableDecl->rangeVariableDeclaration;
214
        $joinAssocPathExpression = new Query\AST\JoinAssociationPathExpression($rangeVariableDecl->aliasIdentificationVariable, 'address');
215
        $joinAssocDeclaration    = new Query\AST\JoinAssociationDeclaration($joinAssocPathExpression, $rangeVariableDecl->aliasIdentificationVariable . 'a', null);
216
        $join                    = new Query\AST\Join(Query\AST\Join::JOIN_TYPE_LEFT, $joinAssocDeclaration);
217
        $selectExpression        = new Query\AST\SelectExpression($rangeVariableDecl->aliasIdentificationVariable . 'a', null, false);
218
        
219
        $identificationVariableDecl->joins[]                = $join;
220
        $selectStatement->selectClause->selectExpressions[] = $selectExpression;
221
222
        $entityManager   = $this->_getQuery()->getEntityManager();
223
        $userMetadata    = $entityManager->getClassMetadata('Doctrine\Tests\Models\CMS\CmsUser');
224
        $addressMetadata = $entityManager->getClassMetadata('Doctrine\Tests\Models\CMS\CmsAddress');
225
226
        $this->setQueryComponent($rangeVariableDecl->aliasIdentificationVariable . 'a',
227
            array(
228
                'metadata'     => $addressMetadata,
229
                'parent'       => $rangeVariableDecl->aliasIdentificationVariable,
230
                'relation'     => $userMetadata->getAssociationMapping('address'),
231
                'map'          => null,
232
                'nestingLevel' => 0,
233
                'token'        => null,
234
            )
235
        );
236
    }
237
}