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