1 | <?php |
||
0 ignored issues
–
show
|
|||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace Doctrine\ORM\Query; |
||
6 | |||
7 | use Doctrine\Common\Persistence\Mapping\MappingException; |
||
8 | use Doctrine\ORM\Mapping\AssociationMetadata; |
||
9 | use Doctrine\ORM\Mapping\FieldMetadata; |
||
10 | use Doctrine\ORM\Mapping\ToOneAssociationMetadata; |
||
11 | use Doctrine\ORM\Query; |
||
12 | use Doctrine\ORM\Query\AST\Functions; |
||
13 | use Doctrine\ORM\Query\AST\Node; |
||
14 | |||
15 | /** |
||
16 | * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language. |
||
17 | * Parses a DQL query, reports any errors in it, and generates an AST. |
||
18 | */ |
||
19 | class Parser |
||
20 | { |
||
21 | /** |
||
22 | * READ-ONLY: Maps BUILT-IN string function names to AST class names. |
||
23 | * |
||
24 | * @var string[] |
||
25 | */ |
||
26 | private static $_STRING_FUNCTIONS = [ |
||
27 | 'concat' => Functions\ConcatFunction::class, |
||
28 | 'substring' => Functions\SubstringFunction::class, |
||
29 | 'trim' => Functions\TrimFunction::class, |
||
30 | 'lower' => Functions\LowerFunction::class, |
||
31 | 'upper' => Functions\UpperFunction::class, |
||
32 | 'identity' => Functions\IdentityFunction::class, |
||
33 | ]; |
||
34 | |||
35 | /** |
||
36 | * READ-ONLY: Maps BUILT-IN numeric function names to AST class names. |
||
37 | * |
||
38 | * @var string[] |
||
39 | */ |
||
40 | private static $_NUMERIC_FUNCTIONS = [ |
||
41 | 'length' => Functions\LengthFunction::class, |
||
42 | 'locate' => Functions\LocateFunction::class, |
||
43 | 'abs' => Functions\AbsFunction::class, |
||
44 | 'sqrt' => Functions\SqrtFunction::class, |
||
45 | 'mod' => Functions\ModFunction::class, |
||
46 | 'size' => Functions\SizeFunction::class, |
||
47 | 'date_diff' => Functions\DateDiffFunction::class, |
||
48 | 'bit_and' => Functions\BitAndFunction::class, |
||
49 | 'bit_or' => Functions\BitOrFunction::class, |
||
50 | |||
51 | // Aggregate functions |
||
52 | 'min' => Functions\MinFunction::class, |
||
53 | 'max' => Functions\MaxFunction::class, |
||
54 | 'avg' => Functions\AvgFunction::class, |
||
55 | 'sum' => Functions\SumFunction::class, |
||
56 | 'count' => Functions\CountFunction::class, |
||
57 | ]; |
||
58 | |||
59 | /** |
||
60 | * READ-ONLY: Maps BUILT-IN datetime function names to AST class names. |
||
61 | * |
||
62 | * @var string[] |
||
63 | */ |
||
64 | private static $_DATETIME_FUNCTIONS = [ |
||
65 | 'current_date' => Functions\CurrentDateFunction::class, |
||
66 | 'current_time' => Functions\CurrentTimeFunction::class, |
||
67 | 'current_timestamp' => Functions\CurrentTimestampFunction::class, |
||
68 | 'date_add' => Functions\DateAddFunction::class, |
||
69 | 'date_sub' => Functions\DateSubFunction::class, |
||
70 | ]; |
||
71 | |||
72 | /* |
||
73 | * Expressions that were encountered during parsing of identifiers and expressions |
||
74 | * and still need to be validated. |
||
75 | */ |
||
76 | |||
77 | /** |
||
78 | * @var mixed[][] |
||
79 | */ |
||
80 | private $deferredIdentificationVariables = []; |
||
81 | |||
82 | /** |
||
83 | * @var mixed[][] |
||
84 | */ |
||
85 | private $deferredPartialObjectExpressions = []; |
||
86 | |||
87 | /** |
||
88 | * @var mixed[][] |
||
89 | */ |
||
90 | private $deferredPathExpressions = []; |
||
91 | |||
92 | /** |
||
93 | * @var mixed[][] |
||
94 | */ |
||
95 | private $deferredResultVariables = []; |
||
96 | |||
97 | /** |
||
98 | * @var mixed[][] |
||
99 | */ |
||
100 | private $deferredNewObjectExpressions = []; |
||
101 | |||
102 | /** |
||
103 | * The lexer. |
||
104 | * |
||
105 | * @var \Doctrine\ORM\Query\Lexer |
||
106 | */ |
||
107 | private $lexer; |
||
108 | |||
109 | /** |
||
110 | * The parser result. |
||
111 | * |
||
112 | * @var \Doctrine\ORM\Query\ParserResult |
||
113 | */ |
||
114 | private $parserResult; |
||
115 | |||
116 | /** |
||
117 | * The EntityManager. |
||
118 | * |
||
119 | * @var \Doctrine\ORM\EntityManagerInterface |
||
120 | */ |
||
121 | private $em; |
||
122 | |||
123 | /** |
||
124 | * The Query to parse. |
||
125 | * |
||
126 | * @var Query |
||
127 | */ |
||
128 | private $query; |
||
129 | |||
130 | /** |
||
131 | * Map of declared query components in the parsed query. |
||
132 | * |
||
133 | * @var mixed[][] |
||
134 | */ |
||
135 | private $queryComponents = []; |
||
136 | |||
137 | /** |
||
138 | * Keeps the nesting level of defined ResultVariables. |
||
139 | * |
||
140 | * @var int |
||
141 | */ |
||
142 | private $nestingLevel = 0; |
||
143 | |||
144 | /** |
||
145 | * Any additional custom tree walkers that modify the AST. |
||
146 | * |
||
147 | * @var string[] |
||
148 | */ |
||
149 | private $customTreeWalkers = []; |
||
150 | |||
151 | /** |
||
152 | * The custom last tree walker, if any, that is responsible for producing the output. |
||
153 | * |
||
154 | * @var TreeWalker |
||
155 | */ |
||
156 | private $customOutputWalker; |
||
157 | |||
158 | /** |
||
159 | * @var Node[] |
||
160 | */ |
||
161 | private $identVariableExpressions = []; |
||
162 | |||
163 | /** |
||
164 | * Creates a new query parser object. |
||
165 | * |
||
166 | * @param Query $query The Query to parse. |
||
167 | */ |
||
168 | 848 | public function __construct(Query $query) |
|
169 | { |
||
170 | 848 | $this->query = $query; |
|
171 | 848 | $this->em = $query->getEntityManager(); |
|
172 | 848 | $this->lexer = new Lexer($query->getDQL()); |
|
173 | 848 | $this->parserResult = new ParserResult(); |
|
174 | 848 | } |
|
175 | |||
176 | /** |
||
177 | * Sets a custom tree walker that produces output. |
||
178 | * This tree walker will be run last over the AST, after any other walkers. |
||
179 | * |
||
180 | * @param string $className |
||
181 | */ |
||
182 | 127 | public function setCustomOutputTreeWalker($className) |
|
183 | { |
||
184 | 127 | $this->customOutputWalker = $className; |
|
185 | 127 | } |
|
186 | |||
187 | /** |
||
188 | * Adds a custom tree walker for modifying the AST. |
||
189 | * |
||
190 | * @param string $className |
||
191 | */ |
||
192 | public function addCustomTreeWalker($className) |
||
193 | { |
||
194 | $this->customTreeWalkers[] = $className; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Gets the lexer used by the parser. |
||
199 | * |
||
200 | * @return \Doctrine\ORM\Query\Lexer |
||
201 | */ |
||
202 | 31 | public function getLexer() |
|
203 | { |
||
204 | 31 | return $this->lexer; |
|
205 | } |
||
206 | |||
207 | /** |
||
208 | * Gets the ParserResult that is being filled with information during parsing. |
||
209 | * |
||
210 | * @return \Doctrine\ORM\Query\ParserResult |
||
211 | */ |
||
212 | public function getParserResult() |
||
213 | { |
||
214 | return $this->parserResult; |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * Gets the EntityManager used by the parser. |
||
219 | * |
||
220 | * @return \Doctrine\ORM\EntityManagerInterface |
||
221 | */ |
||
222 | 17 | public function getEntityManager() |
|
223 | { |
||
224 | 17 | return $this->em; |
|
225 | } |
||
226 | |||
227 | /** |
||
228 | * Parses and builds AST for the given Query. |
||
229 | * |
||
230 | * @return \Doctrine\ORM\Query\AST\SelectStatement | |
||
231 | * \Doctrine\ORM\Query\AST\UpdateStatement | |
||
232 | * \Doctrine\ORM\Query\AST\DeleteStatement |
||
233 | */ |
||
234 | 848 | public function getAST() |
|
235 | { |
||
236 | // Parse & build AST |
||
237 | 848 | $AST = $this->QueryLanguage(); |
|
238 | |||
239 | // Process any deferred validations of some nodes in the AST. |
||
240 | // This also allows post-processing of the AST for modification purposes. |
||
241 | 806 | $this->processDeferredIdentificationVariables(); |
|
242 | |||
243 | 804 | if ($this->deferredPartialObjectExpressions) { |
|
244 | 9 | $this->processDeferredPartialObjectExpressions(); |
|
245 | } |
||
246 | |||
247 | 803 | if ($this->deferredPathExpressions) { |
|
248 | 596 | $this->processDeferredPathExpressions(); |
|
249 | } |
||
250 | |||
251 | 801 | if ($this->deferredResultVariables) { |
|
252 | 32 | $this->processDeferredResultVariables(); |
|
253 | } |
||
254 | |||
255 | 801 | if ($this->deferredNewObjectExpressions) { |
|
256 | 28 | $this->processDeferredNewObjectExpressions($AST); |
|
257 | } |
||
258 | |||
259 | 797 | $this->processRootEntityAliasSelected(); |
|
260 | |||
261 | // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot! |
||
262 | 796 | $this->fixIdentificationVariableOrder($AST); |
|
263 | |||
264 | 796 | return $AST; |
|
265 | } |
||
266 | |||
267 | /** |
||
268 | * Attempts to match the given token with the current lookahead token. |
||
269 | * |
||
270 | * If they match, updates the lookahead token; otherwise raises a syntax |
||
271 | * error. |
||
272 | * |
||
273 | * @param int $token The token type. |
||
274 | * |
||
275 | * @throws QueryException If the tokens don't match. |
||
276 | */ |
||
277 | 859 | public function match($token) |
|
278 | { |
||
279 | 859 | $lookaheadType = $this->lexer->lookahead['type']; |
|
280 | |||
281 | // Short-circuit on first condition, usually types match |
||
282 | 859 | if ($lookaheadType !== $token) { |
|
283 | // If parameter is not identifier (1-99) must be exact match |
||
284 | 21 | if ($token < Lexer::T_IDENTIFIER) { |
|
285 | 3 | $this->syntaxError($this->lexer->getLiteral($token)); |
|
286 | } |
||
287 | |||
288 | // If parameter is keyword (200+) must be exact match |
||
289 | 18 | if ($token > Lexer::T_IDENTIFIER) { |
|
290 | 7 | $this->syntaxError($this->lexer->getLiteral($token)); |
|
291 | } |
||
292 | |||
293 | // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+) |
||
294 | 11 | if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) { |
|
295 | 8 | $this->syntaxError($this->lexer->getLiteral($token)); |
|
296 | } |
||
297 | } |
||
298 | |||
299 | 852 | $this->lexer->moveNext(); |
|
300 | 852 | } |
|
301 | |||
302 | /** |
||
303 | * Frees this parser, enabling it to be reused. |
||
304 | * |
||
305 | * @param bool $deep Whether to clean peek and reset errors. |
||
306 | * @param int $position Position to reset. |
||
307 | */ |
||
308 | public function free($deep = false, $position = 0) |
||
309 | { |
||
310 | // WARNING! Use this method with care. It resets the scanner! |
||
311 | $this->lexer->resetPosition($position); |
||
312 | |||
313 | // Deep = true cleans peek and also any previously defined errors |
||
314 | if ($deep) { |
||
315 | $this->lexer->resetPeek(); |
||
316 | } |
||
317 | |||
318 | $this->lexer->token = null; |
||
319 | $this->lexer->lookahead = null; |
||
320 | } |
||
321 | |||
322 | /** |
||
323 | * Parses a query string. |
||
324 | * |
||
325 | * @return ParserResult |
||
326 | */ |
||
327 | 848 | public function parse() |
|
328 | { |
||
329 | 848 | $AST = $this->getAST(); |
|
330 | |||
331 | 796 | $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS); |
|
332 | 796 | if ($customWalkers !== false) { |
|
333 | 96 | $this->customTreeWalkers = $customWalkers; |
|
334 | } |
||
335 | |||
336 | 796 | $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER); |
|
337 | |||
338 | 796 | if ($customOutputWalker !== false) { |
|
339 | 79 | $this->customOutputWalker = $customOutputWalker; |
|
340 | } |
||
341 | |||
342 | // Run any custom tree walkers over the AST |
||
343 | 796 | if ($this->customTreeWalkers) { |
|
344 | 95 | $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents); |
|
345 | |||
346 | 95 | foreach ($this->customTreeWalkers as $walker) { |
|
347 | 95 | $treeWalkerChain->addTreeWalker($walker); |
|
348 | } |
||
349 | |||
350 | switch (true) { |
||
351 | 95 | case ($AST instanceof AST\UpdateStatement): |
|
352 | $treeWalkerChain->walkUpdateStatement($AST); |
||
353 | break; |
||
354 | |||
355 | 95 | case ($AST instanceof AST\DeleteStatement): |
|
356 | $treeWalkerChain->walkDeleteStatement($AST); |
||
357 | break; |
||
358 | |||
359 | 95 | case ($AST instanceof AST\SelectStatement): |
|
360 | default: |
||
361 | 95 | $treeWalkerChain->walkSelectStatement($AST); |
|
362 | } |
||
363 | |||
364 | 89 | $this->queryComponents = $treeWalkerChain->getQueryComponents(); |
|
365 | } |
||
366 | |||
367 | 790 | $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class; |
|
368 | 790 | $outputWalker = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents); |
|
369 | |||
370 | // Assign an SQL executor to the parser result |
||
371 | 790 | $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST)); |
|
372 | |||
373 | 782 | return $this->parserResult; |
|
374 | } |
||
375 | |||
376 | /** |
||
377 | * Fixes order of identification variables. |
||
378 | * |
||
379 | * They have to appear in the select clause in the same order as the |
||
380 | * declarations (from ... x join ... y join ... z ...) appear in the query |
||
381 | * as the hydration process relies on that order for proper operation. |
||
382 | * |
||
383 | * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST |
||
384 | */ |
||
385 | 796 | private function fixIdentificationVariableOrder($AST) |
|
386 | { |
||
387 | 796 | if (count($this->identVariableExpressions) <= 1) { |
|
388 | 621 | return; |
|
389 | } |
||
390 | |||
391 | 180 | foreach ($this->queryComponents as $dqlAlias => $qComp) { |
|
392 | 180 | if (! isset($this->identVariableExpressions[$dqlAlias])) { |
|
393 | 8 | continue; |
|
394 | } |
||
395 | |||
396 | 180 | $expr = $this->identVariableExpressions[$dqlAlias]; |
|
397 | 180 | $key = array_search($expr, $AST->selectClause->selectExpressions); |
|
398 | |||
399 | 180 | unset($AST->selectClause->selectExpressions[$key]); |
|
400 | |||
401 | 180 | $AST->selectClause->selectExpressions[] = $expr; |
|
402 | } |
||
403 | 180 | } |
|
404 | |||
405 | /** |
||
406 | * Generates a new syntax error. |
||
407 | * |
||
408 | * @param string $expected Expected string. |
||
409 | * @param mixed[]|null $token Got token. |
||
410 | * |
||
411 | * @throws \Doctrine\ORM\Query\QueryException |
||
412 | */ |
||
413 | 18 | public function syntaxError($expected = '', $token = null) |
|
414 | { |
||
415 | 18 | if ($token === null) { |
|
416 | 15 | $token = $this->lexer->lookahead; |
|
417 | } |
||
418 | |||
419 | 18 | $tokenPos = $token['position'] ?? '-1'; |
|
420 | |||
421 | 18 | $message = sprintf('line 0, col %d: Error: ', $tokenPos); |
|
422 | 18 | $message .= ($expected !== '') ? sprintf('Expected %s, got ', $expected) : 'Unexpected '; |
|
423 | 18 | $message .= ($this->lexer->lookahead === null) ? 'end of string.' : sprintf("'%s'", $token['value']); |
|
424 | |||
425 | 18 | throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL())); |
|
426 | } |
||
427 | |||
428 | /** |
||
429 | * Generates a new semantical error. |
||
430 | * |
||
431 | * @param string $message Optional message. |
||
432 | * @param mixed[]|null $token Optional token. |
||
433 | * |
||
434 | * @throws \Doctrine\ORM\Query\QueryException |
||
435 | */ |
||
436 | 33 | public function semanticalError($message = '', $token = null, ?\Throwable $previousFailure = null) |
|
437 | { |
||
438 | 33 | if ($token === null) { |
|
439 | 2 | $token = $this->lexer->lookahead; |
|
440 | } |
||
441 | |||
442 | // Minimum exposed chars ahead of token |
||
443 | 33 | $distance = 12; |
|
444 | |||
445 | // Find a position of a final word to display in error string |
||
446 | 33 | $dql = $this->query->getDQL(); |
|
447 | 33 | $length = strlen($dql); |
|
448 | 33 | $pos = $token['position'] + $distance; |
|
449 | 33 | $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length); |
|
450 | 33 | $length = ($pos !== false) ? $pos - $token['position'] : $distance; |
|
451 | |||
452 | 33 | $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1'; |
|
453 | 33 | $tokenStr = substr($dql, (int) $token['position'], $length); |
|
454 | |||
455 | // Building informative message |
||
456 | 33 | $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message; |
|
457 | |||
458 | 33 | throw QueryException::semanticalError( |
|
459 | 33 | $message, |
|
460 | 33 | QueryException::dqlError($this->query->getDQL(), $previousFailure) |
|
461 | ); |
||
462 | } |
||
463 | |||
464 | /** |
||
465 | * Peeks beyond the matched closing parenthesis and returns the first token after that one. |
||
466 | * |
||
467 | * @param bool $resetPeek Reset peek after finding the closing parenthesis. |
||
468 | * |
||
469 | * @return mixed[] |
||
470 | */ |
||
471 | 171 | private function peekBeyondClosingParenthesis($resetPeek = true) |
|
472 | { |
||
473 | 171 | $token = $this->lexer->peek(); |
|
474 | 171 | $numUnmatched = 1; |
|
475 | |||
476 | 171 | while ($numUnmatched > 0 && $token !== null) { |
|
477 | 170 | switch ($token['type']) { |
|
478 | case Lexer::T_OPEN_PARENTHESIS: |
||
479 | 42 | ++$numUnmatched; |
|
480 | 42 | break; |
|
481 | |||
482 | case Lexer::T_CLOSE_PARENTHESIS: |
||
483 | 170 | --$numUnmatched; |
|
484 | 170 | break; |
|
485 | |||
486 | default: |
||
487 | // Do nothing |
||
488 | } |
||
489 | |||
490 | 170 | $token = $this->lexer->peek(); |
|
491 | } |
||
492 | |||
493 | 171 | if ($resetPeek) { |
|
494 | 150 | $this->lexer->resetPeek(); |
|
495 | } |
||
496 | |||
497 | 171 | return $token; |
|
498 | } |
||
499 | |||
500 | /** |
||
501 | * Checks if the given token indicates a mathematical operator. |
||
502 | * |
||
503 | * @param mixed[] $token |
||
504 | * |
||
505 | * @return bool TRUE if the token is a mathematical operator, FALSE otherwise. |
||
506 | */ |
||
507 | 359 | private function isMathOperator($token) |
|
508 | { |
||
509 | 359 | return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]); |
|
510 | } |
||
511 | |||
512 | /** |
||
513 | * Checks if the next-next (after lookahead) token starts a function. |
||
514 | * |
||
515 | * @return bool TRUE if the next-next tokens start a function, FALSE otherwise. |
||
516 | */ |
||
517 | 402 | private function isFunction() |
|
518 | { |
||
519 | 402 | $lookaheadType = $this->lexer->lookahead['type']; |
|
520 | 402 | $peek = $this->lexer->peek(); |
|
521 | |||
522 | 402 | $this->lexer->resetPeek(); |
|
523 | |||
524 | 402 | return $lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS; |
|
525 | } |
||
526 | |||
527 | /** |
||
528 | * Checks whether the given token type indicates an aggregate function. |
||
529 | * |
||
530 | * @param int $tokenType |
||
531 | * |
||
532 | * @return bool TRUE if the token type is an aggregate function, FALSE otherwise. |
||
533 | */ |
||
534 | 1 | private function isAggregateFunction($tokenType) |
|
535 | { |
||
536 | 1 | return in_array($tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT]); |
|
537 | } |
||
538 | |||
539 | /** |
||
540 | * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME. |
||
541 | * |
||
542 | * @return bool |
||
543 | */ |
||
544 | 301 | private function isNextAllAnySome() |
|
545 | { |
||
546 | 301 | return in_array($this->lexer->lookahead['type'], [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME]); |
|
547 | } |
||
548 | |||
549 | /** |
||
550 | * Validates that the given <tt>IdentificationVariable</tt> is semantically correct. |
||
551 | * It must exist in query components list. |
||
552 | */ |
||
553 | 806 | private function processDeferredIdentificationVariables() |
|
554 | { |
||
555 | 806 | foreach ($this->deferredIdentificationVariables as $deferredItem) { |
|
556 | 785 | $identVariable = $deferredItem['expression']; |
|
557 | |||
558 | // Check if IdentificationVariable exists in queryComponents |
||
559 | 785 | if (! isset($this->queryComponents[$identVariable])) { |
|
560 | 1 | $this->semanticalError( |
|
561 | 1 | sprintf("'%s' is not defined.", $identVariable), |
|
562 | 1 | $deferredItem['token'] |
|
563 | ); |
||
564 | } |
||
565 | |||
566 | 785 | $qComp = $this->queryComponents[$identVariable]; |
|
567 | |||
568 | // Check if queryComponent points to an AbstractSchemaName or a ResultVariable |
||
569 | 785 | if (! isset($qComp['metadata'])) { |
|
570 | $this->semanticalError( |
||
571 | sprintf("'%s' does not point to a Class.", $identVariable), |
||
572 | $deferredItem['token'] |
||
573 | ); |
||
574 | } |
||
575 | |||
576 | // Validate if identification variable nesting level is lower or equal than the current one |
||
577 | 785 | if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { |
|
578 | 1 | $this->semanticalError( |
|
579 | 1 | sprintf("'%s' is used outside the scope of its declaration.", $identVariable), |
|
580 | 785 | $deferredItem['token'] |
|
581 | ); |
||
582 | } |
||
583 | } |
||
584 | 804 | } |
|
585 | |||
586 | /** |
||
587 | * Validates that the given <tt>NewObjectExpression</tt>. |
||
588 | * |
||
589 | * @param \Doctrine\ORM\Query\AST\SelectClause $AST |
||
590 | */ |
||
591 | 28 | private function processDeferredNewObjectExpressions($AST) |
|
592 | { |
||
593 | 28 | foreach ($this->deferredNewObjectExpressions as $deferredItem) { |
|
594 | 28 | $expression = $deferredItem['expression']; |
|
595 | 28 | $token = $deferredItem['token']; |
|
596 | 28 | $className = $expression->className; |
|
597 | 28 | $args = $expression->args; |
|
598 | 28 | $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null; |
|
599 | |||
600 | // If the namespace is not given then assumes the first FROM entity namespace |
||
601 | 28 | if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) { |
|
602 | 11 | $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\')); |
|
603 | 11 | $fqcn = $namespace . '\\' . $className; |
|
604 | |||
605 | 11 | if (class_exists($fqcn)) { |
|
606 | 11 | $expression->className = $fqcn; |
|
607 | 11 | $className = $fqcn; |
|
608 | } |
||
609 | } |
||
610 | |||
611 | 28 | if (! class_exists($className)) { |
|
612 | 1 | $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token); |
|
613 | } |
||
614 | |||
615 | 27 | $class = new \ReflectionClass($className); |
|
616 | |||
617 | 27 | if (! $class->isInstantiable()) { |
|
618 | 1 | $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token); |
|
619 | } |
||
620 | |||
621 | 26 | if ($class->getConstructor() === null) { |
|
622 | 1 | $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token); |
|
623 | } |
||
624 | |||
625 | 25 | if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) { |
|
626 | 25 | $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token); |
|
627 | } |
||
628 | } |
||
629 | 24 | } |
|
630 | |||
631 | /** |
||
632 | * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct. |
||
633 | * It must exist in query components list. |
||
634 | */ |
||
635 | 9 | private function processDeferredPartialObjectExpressions() |
|
636 | { |
||
637 | 9 | foreach ($this->deferredPartialObjectExpressions as $deferredItem) { |
|
638 | 9 | $expr = $deferredItem['expression']; |
|
639 | 9 | $class = $this->queryComponents[$expr->identificationVariable]['metadata']; |
|
640 | |||
641 | 9 | foreach ($expr->partialFieldSet as $field) { |
|
642 | 9 | $property = $class->getProperty($field); |
|
643 | |||
644 | 9 | if ($property instanceof FieldMetadata || |
|
645 | 9 | ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) { |
|
646 | 9 | continue; |
|
647 | } |
||
648 | |||
649 | $this->semanticalError( |
||
650 | sprintf("There is no mapped field named '%s' on class %s.", $field, $class->getClassName()), |
||
651 | $deferredItem['token'] |
||
652 | ); |
||
653 | } |
||
654 | |||
655 | 9 | if (array_intersect($class->identifier, $expr->partialFieldSet) !== $class->identifier) { |
|
656 | 1 | $this->semanticalError( |
|
657 | 1 | sprintf('The partial field selection of class %s must contain the identifier.', $class->getClassName()), |
|
658 | 9 | $deferredItem['token'] |
|
659 | ); |
||
660 | } |
||
661 | } |
||
662 | 8 | } |
|
663 | |||
664 | /** |
||
665 | * Validates that the given <tt>ResultVariable</tt> is semantically correct. |
||
666 | * It must exist in query components list. |
||
667 | */ |
||
668 | 32 | private function processDeferredResultVariables() |
|
669 | { |
||
670 | 32 | foreach ($this->deferredResultVariables as $deferredItem) { |
|
671 | 32 | $resultVariable = $deferredItem['expression']; |
|
672 | |||
673 | // Check if ResultVariable exists in queryComponents |
||
674 | 32 | if (! isset($this->queryComponents[$resultVariable])) { |
|
675 | $this->semanticalError( |
||
676 | sprintf("'%s' is not defined.", $resultVariable), |
||
677 | $deferredItem['token'] |
||
678 | ); |
||
679 | } |
||
680 | |||
681 | 32 | $qComp = $this->queryComponents[$resultVariable]; |
|
682 | |||
683 | // Check if queryComponent points to an AbstractSchemaName or a ResultVariable |
||
684 | 32 | if (! isset($qComp['resultVariable'])) { |
|
685 | $this->semanticalError( |
||
686 | sprintf("'%s' does not point to a ResultVariable.", $resultVariable), |
||
687 | $deferredItem['token'] |
||
688 | ); |
||
689 | } |
||
690 | |||
691 | // Validate if identification variable nesting level is lower or equal than the current one |
||
692 | 32 | if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) { |
|
693 | $this->semanticalError( |
||
694 | sprintf("'%s' is used outside the scope of its declaration.", $resultVariable), |
||
695 | 32 | $deferredItem['token'] |
|
696 | ); |
||
697 | } |
||
698 | } |
||
699 | 32 | } |
|
700 | |||
701 | /** |
||
702 | * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules: |
||
703 | * |
||
704 | * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression |
||
705 | * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression |
||
706 | * StateFieldPathExpression ::= IdentificationVariable "." StateField |
||
707 | * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField |
||
708 | * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField |
||
709 | */ |
||
710 | 596 | private function processDeferredPathExpressions() |
|
711 | { |
||
712 | 596 | foreach ($this->deferredPathExpressions as $deferredItem) { |
|
713 | 596 | $pathExpression = $deferredItem['expression']; |
|
714 | |||
715 | 596 | $qComp = $this->queryComponents[$pathExpression->identificationVariable]; |
|
716 | 596 | $class = $qComp['metadata']; |
|
717 | 596 | $field = $pathExpression->field; |
|
718 | |||
719 | 596 | if ($field === null) { |
|
720 | 40 | $field = $pathExpression->field = $class->identifier[0]; |
|
721 | } |
||
722 | |||
723 | 596 | $property = $class->getProperty($field); |
|
724 | |||
725 | // Check if field or association exists |
||
726 | 596 | if (! $property) { |
|
727 | $this->semanticalError( |
||
728 | 'Class ' . $class->getClassName() . ' has no field or association named ' . $field, |
||
729 | $deferredItem['token'] |
||
730 | ); |
||
731 | } |
||
732 | |||
733 | 596 | $fieldType = AST\PathExpression::TYPE_STATE_FIELD; |
|
734 | |||
735 | 596 | if ($property instanceof AssociationMetadata) { |
|
736 | 89 | $fieldType = $property instanceof ToOneAssociationMetadata |
|
737 | 66 | ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
|
738 | 89 | : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION |
|
739 | ; |
||
740 | } |
||
741 | |||
742 | // Validate if PathExpression is one of the expected types |
||
743 | 596 | $expectedType = $pathExpression->expectedType; |
|
744 | |||
745 | 596 | if (! ($expectedType & $fieldType)) { |
|
746 | // We need to recognize which was expected type(s) |
||
747 | 2 | $expectedStringTypes = []; |
|
748 | |||
749 | // Validate state field type |
||
750 | 2 | if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) { |
|
751 | 1 | $expectedStringTypes[] = 'StateFieldPathExpression'; |
|
752 | } |
||
753 | |||
754 | // Validate single valued association (*-to-one) |
||
755 | 2 | if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) { |
|
756 | 2 | $expectedStringTypes[] = 'SingleValuedAssociationField'; |
|
757 | } |
||
758 | |||
759 | // Validate single valued association (*-to-many) |
||
760 | 2 | if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) { |
|
761 | $expectedStringTypes[] = 'CollectionValuedAssociationField'; |
||
762 | } |
||
763 | |||
764 | // Build the error message |
||
765 | 2 | $semanticalError = 'Invalid PathExpression. '; |
|
766 | 2 | $semanticalError .= \count($expectedStringTypes) === 1 |
|
767 | 1 | ? 'Must be a ' . $expectedStringTypes[0] . '.' |
|
768 | 2 | : implode(' or ', $expectedStringTypes) . ' expected.'; |
|
769 | |||
770 | 2 | $this->semanticalError($semanticalError, $deferredItem['token']); |
|
771 | } |
||
772 | |||
773 | // We need to force the type in PathExpression |
||
774 | 594 | $pathExpression->type = $fieldType; |
|
775 | } |
||
776 | 594 | } |
|
777 | |||
778 | 797 | private function processRootEntityAliasSelected() |
|
779 | { |
||
780 | 797 | if (! $this->identVariableExpressions) { |
|
781 | 236 | return; |
|
782 | } |
||
783 | |||
784 | 571 | foreach ($this->identVariableExpressions as $dqlAlias => $expr) { |
|
785 | 571 | if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) { |
|
786 | 571 | return; |
|
787 | } |
||
788 | } |
||
789 | |||
790 | 1 | $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.'); |
|
791 | } |
||
792 | |||
793 | /** |
||
794 | * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement |
||
795 | * |
||
796 | * @return \Doctrine\ORM\Query\AST\SelectStatement | |
||
797 | * \Doctrine\ORM\Query\AST\UpdateStatement | |
||
798 | * \Doctrine\ORM\Query\AST\DeleteStatement |
||
799 | */ |
||
800 | 848 | public function QueryLanguage() |
|
801 | { |
||
802 | 848 | $statement = null; |
|
803 | |||
804 | 848 | $this->lexer->moveNext(); |
|
805 | |||
806 | 848 | switch ($this->lexer->lookahead['type']) { |
|
807 | case Lexer::T_SELECT: |
||
808 | 783 | $statement = $this->SelectStatement(); |
|
809 | 745 | break; |
|
810 | |||
811 | case Lexer::T_UPDATE: |
||
812 | 32 | $statement = $this->UpdateStatement(); |
|
813 | 32 | break; |
|
814 | |||
815 | case Lexer::T_DELETE: |
||
816 | 41 | $statement = $this->DeleteStatement(); |
|
817 | 40 | break; |
|
818 | |||
819 | default: |
||
820 | 2 | $this->syntaxError('SELECT, UPDATE or DELETE'); |
|
821 | break; |
||
822 | } |
||
823 | |||
824 | // Check for end of string |
||
825 | 809 | if ($this->lexer->lookahead !== null) { |
|
826 | 3 | $this->syntaxError('end of string'); |
|
827 | } |
||
828 | |||
829 | 806 | return $statement; |
|
830 | } |
||
831 | |||
832 | /** |
||
833 | * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] |
||
834 | * |
||
835 | * @return \Doctrine\ORM\Query\AST\SelectStatement |
||
836 | */ |
||
837 | 783 | public function SelectStatement() |
|
838 | { |
||
839 | 783 | $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause()); |
|
840 | |||
841 | 749 | $selectStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; |
|
842 | 746 | $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; |
|
843 | 745 | $selectStatement->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; |
|
844 | 745 | $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; |
|
845 | |||
846 | 745 | return $selectStatement; |
|
847 | } |
||
848 | |||
849 | /** |
||
850 | * UpdateStatement ::= UpdateClause [WhereClause] |
||
851 | * |
||
852 | * @return \Doctrine\ORM\Query\AST\UpdateStatement |
||
853 | */ |
||
854 | 32 | public function UpdateStatement() |
|
855 | { |
||
856 | 32 | $updateStatement = new AST\UpdateStatement($this->UpdateClause()); |
|
857 | |||
858 | 32 | $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; |
|
859 | |||
860 | 32 | return $updateStatement; |
|
861 | } |
||
862 | |||
863 | /** |
||
864 | * DeleteStatement ::= DeleteClause [WhereClause] |
||
865 | * |
||
866 | * @return \Doctrine\ORM\Query\AST\DeleteStatement |
||
867 | */ |
||
868 | 41 | public function DeleteStatement() |
|
869 | { |
||
870 | 41 | $deleteStatement = new AST\DeleteStatement($this->DeleteClause()); |
|
871 | |||
872 | 40 | $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; |
|
873 | |||
874 | 40 | return $deleteStatement; |
|
875 | } |
||
876 | |||
877 | /** |
||
878 | * IdentificationVariable ::= identifier |
||
879 | * |
||
880 | * @return string |
||
881 | */ |
||
882 | 816 | public function IdentificationVariable() |
|
883 | { |
||
884 | 816 | $this->match(Lexer::T_IDENTIFIER); |
|
885 | |||
886 | 816 | $identVariable = $this->lexer->token['value']; |
|
887 | |||
888 | 816 | $this->deferredIdentificationVariables[] = [ |
|
889 | 816 | 'expression' => $identVariable, |
|
890 | 816 | 'nestingLevel' => $this->nestingLevel, |
|
891 | 816 | 'token' => $this->lexer->token, |
|
892 | ]; |
||
893 | |||
894 | 816 | return $identVariable; |
|
895 | } |
||
896 | |||
897 | /** |
||
898 | * AliasIdentificationVariable = identifier |
||
899 | * |
||
900 | * @return string |
||
901 | */ |
||
902 | 817 | public function AliasIdentificationVariable() |
|
903 | { |
||
904 | 817 | $this->match(Lexer::T_IDENTIFIER); |
|
905 | |||
906 | 817 | $aliasIdentVariable = $this->lexer->token['value']; |
|
907 | 817 | $exists = isset($this->queryComponents[$aliasIdentVariable]); |
|
908 | |||
909 | 817 | if ($exists) { |
|
910 | 2 | $this->semanticalError(sprintf("'%s' is already defined.", $aliasIdentVariable), $this->lexer->token); |
|
911 | } |
||
912 | |||
913 | 817 | return $aliasIdentVariable; |
|
914 | } |
||
915 | |||
916 | /** |
||
917 | * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier |
||
918 | * |
||
919 | * @return string |
||
920 | */ |
||
921 | 838 | public function AbstractSchemaName() |
|
922 | { |
||
923 | 838 | if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) { |
|
924 | 820 | $this->match(Lexer::T_FULLY_QUALIFIED_NAME); |
|
925 | |||
926 | 820 | $schemaName = $this->lexer->token['value']; |
|
927 | 29 | } elseif ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { |
|
928 | 19 | $this->match(Lexer::T_IDENTIFIER); |
|
929 | |||
930 | 19 | $schemaName = $this->lexer->token['value']; |
|
931 | } else { |
||
932 | 11 | $this->match(Lexer::T_ALIASED_NAME); |
|
933 | |||
934 | 10 | list($namespaceAlias, $simpleClassName) = explode(':', $this->lexer->token['value']); |
|
935 | |||
936 | 10 | $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName; |
|
937 | } |
||
938 | |||
939 | 837 | return $schemaName; |
|
940 | } |
||
941 | |||
942 | /** |
||
943 | * Validates an AbstractSchemaName, making sure the class exists. |
||
944 | * |
||
945 | * @param string $schemaName The name to validate. |
||
946 | * |
||
947 | * @throws QueryException If the name does not exist. |
||
948 | */ |
||
949 | 832 | private function validateAbstractSchemaName($schemaName) : void |
|
950 | { |
||
951 | 832 | if (class_exists($schemaName, true) || interface_exists($schemaName, true)) { |
|
952 | 816 | return; |
|
953 | } |
||
954 | |||
955 | try { |
||
956 | 17 | $this->getEntityManager()->getClassMetadata($schemaName); |
|
957 | |||
958 | 1 | return; |
|
959 | 16 | } catch (MappingException $mappingException) { |
|
960 | 16 | $this->semanticalError( |
|
961 | 16 | \sprintf('Class %s could not be mapped', $schemaName), |
|
962 | 16 | $this->lexer->token |
|
963 | ); |
||
964 | } |
||
965 | |||
966 | $this->semanticalError(sprintf("Class '%s' is not defined.", $schemaName), $this->lexer->token); |
||
967 | } |
||
968 | |||
969 | /** |
||
970 | * AliasResultVariable ::= identifier |
||
971 | * |
||
972 | * @return string |
||
973 | */ |
||
974 | 130 | public function AliasResultVariable() |
|
975 | { |
||
976 | 130 | $this->match(Lexer::T_IDENTIFIER); |
|
977 | |||
978 | 126 | $resultVariable = $this->lexer->token['value']; |
|
979 | 126 | $exists = isset($this->queryComponents[$resultVariable]); |
|
980 | |||
981 | 126 | if ($exists) { |
|
982 | 2 | $this->semanticalError(sprintf("'%s' is already defined.", $resultVariable), $this->lexer->token); |
|
983 | } |
||
984 | |||
985 | 126 | return $resultVariable; |
|
986 | } |
||
987 | |||
988 | /** |
||
989 | * ResultVariable ::= identifier |
||
990 | * |
||
991 | * @return string |
||
992 | */ |
||
993 | 32 | public function ResultVariable() |
|
994 | { |
||
995 | 32 | $this->match(Lexer::T_IDENTIFIER); |
|
996 | |||
997 | 32 | $resultVariable = $this->lexer->token['value']; |
|
998 | |||
999 | // Defer ResultVariable validation |
||
1000 | 32 | $this->deferredResultVariables[] = [ |
|
1001 | 32 | 'expression' => $resultVariable, |
|
1002 | 32 | 'nestingLevel' => $this->nestingLevel, |
|
1003 | 32 | 'token' => $this->lexer->token, |
|
1004 | ]; |
||
1005 | |||
1006 | 32 | return $resultVariable; |
|
1007 | } |
||
1008 | |||
1009 | /** |
||
1010 | * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) |
||
1011 | * |
||
1012 | * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression |
||
1013 | */ |
||
1014 | 258 | public function JoinAssociationPathExpression() |
|
1015 | { |
||
1016 | 258 | $identVariable = $this->IdentificationVariable(); |
|
1017 | |||
1018 | 258 | if (! isset($this->queryComponents[$identVariable])) { |
|
1019 | $this->semanticalError( |
||
1020 | 'Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.' |
||
1021 | ); |
||
1022 | } |
||
1023 | |||
1024 | 258 | $this->match(Lexer::T_DOT); |
|
1025 | 258 | $this->match(Lexer::T_IDENTIFIER); |
|
1026 | |||
1027 | 258 | $field = $this->lexer->token['value']; |
|
1028 | |||
1029 | // Validate association field |
||
1030 | 258 | $qComp = $this->queryComponents[$identVariable]; |
|
1031 | 258 | $class = $qComp['metadata']; |
|
1032 | 258 | $property = $class->getProperty($field); |
|
1033 | |||
1034 | 258 | if (! ($property !== null && $property instanceof AssociationMetadata)) { |
|
1035 | $this->semanticalError('Class ' . $class->getClassName() . ' has no association named ' . $field); |
||
1036 | } |
||
1037 | |||
1038 | 258 | return new AST\JoinAssociationPathExpression($identVariable, $field); |
|
1039 | } |
||
1040 | |||
1041 | /** |
||
1042 | * Parses an arbitrary path expression and defers semantical validation |
||
1043 | * based on expected types. |
||
1044 | * |
||
1045 | * PathExpression ::= IdentificationVariable {"." identifier}* |
||
1046 | * |
||
1047 | * @param int $expectedTypes |
||
1048 | * |
||
1049 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1050 | */ |
||
1051 | 606 | public function PathExpression($expectedTypes) |
|
1052 | { |
||
1053 | 606 | $identVariable = $this->IdentificationVariable(); |
|
1054 | 606 | $field = null; |
|
1055 | |||
1056 | 606 | if ($this->lexer->isNextToken(Lexer::T_DOT)) { |
|
1057 | 600 | $this->match(Lexer::T_DOT); |
|
1058 | 600 | $this->match(Lexer::T_IDENTIFIER); |
|
1059 | |||
1060 | 600 | $field = $this->lexer->token['value']; |
|
1061 | |||
1062 | 600 | while ($this->lexer->isNextToken(Lexer::T_DOT)) { |
|
1063 | $this->match(Lexer::T_DOT); |
||
1064 | $this->match(Lexer::T_IDENTIFIER); |
||
1065 | $field .= '.' . $this->lexer->token['value']; |
||
1066 | } |
||
1067 | } |
||
1068 | |||
1069 | // Creating AST node |
||
1070 | 606 | $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field); |
|
1071 | |||
1072 | // Defer PathExpression validation if requested to be deferred |
||
1073 | 606 | $this->deferredPathExpressions[] = [ |
|
1074 | 606 | 'expression' => $pathExpr, |
|
1075 | 606 | 'nestingLevel' => $this->nestingLevel, |
|
1076 | 606 | 'token' => $this->lexer->token, |
|
1077 | ]; |
||
1078 | |||
1079 | 606 | return $pathExpr; |
|
1080 | } |
||
1081 | |||
1082 | /** |
||
1083 | * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression |
||
1084 | * |
||
1085 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1086 | */ |
||
1087 | public function AssociationPathExpression() |
||
1088 | { |
||
1089 | return $this->PathExpression( |
||
1090 | AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION | |
||
1091 | AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION |
||
1092 | ); |
||
1093 | } |
||
1094 | |||
1095 | /** |
||
1096 | * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression |
||
1097 | * |
||
1098 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1099 | */ |
||
1100 | 515 | public function SingleValuedPathExpression() |
|
1101 | { |
||
1102 | 515 | return $this->PathExpression( |
|
1103 | 515 | AST\PathExpression::TYPE_STATE_FIELD | |
|
1104 | 515 | AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
|
1105 | ); |
||
1106 | } |
||
1107 | |||
1108 | /** |
||
1109 | * StateFieldPathExpression ::= IdentificationVariable "." StateField |
||
1110 | * |
||
1111 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1112 | */ |
||
1113 | 207 | public function StateFieldPathExpression() |
|
1114 | { |
||
1115 | 207 | return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD); |
|
1116 | } |
||
1117 | |||
1118 | /** |
||
1119 | * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField |
||
1120 | * |
||
1121 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1122 | */ |
||
1123 | 9 | public function SingleValuedAssociationPathExpression() |
|
1124 | { |
||
1125 | 9 | return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION); |
|
1126 | } |
||
1127 | |||
1128 | /** |
||
1129 | * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField |
||
1130 | * |
||
1131 | * @return \Doctrine\ORM\Query\AST\PathExpression |
||
1132 | */ |
||
1133 | 23 | public function CollectionValuedPathExpression() |
|
1134 | { |
||
1135 | 23 | return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION); |
|
1136 | } |
||
1137 | |||
1138 | /** |
||
1139 | * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression} |
||
1140 | * |
||
1141 | * @return \Doctrine\ORM\Query\AST\SelectClause |
||
1142 | */ |
||
1143 | 783 | public function SelectClause() |
|
1144 | { |
||
1145 | 783 | $isDistinct = false; |
|
1146 | 783 | $this->match(Lexer::T_SELECT); |
|
1147 | |||
1148 | // Check for DISTINCT |
||
1149 | 783 | if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { |
|
1150 | 6 | $this->match(Lexer::T_DISTINCT); |
|
1151 | |||
1152 | 6 | $isDistinct = true; |
|
1153 | } |
||
1154 | |||
1155 | // Process SelectExpressions (1..N) |
||
1156 | 783 | $selectExpressions = []; |
|
1157 | 783 | $selectExpressions[] = $this->SelectExpression(); |
|
1158 | |||
1159 | 775 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
1160 | 296 | $this->match(Lexer::T_COMMA); |
|
1161 | |||
1162 | 296 | $selectExpressions[] = $this->SelectExpression(); |
|
1163 | } |
||
1164 | |||
1165 | 774 | return new AST\SelectClause($selectExpressions, $isDistinct); |
|
1166 | } |
||
1167 | |||
1168 | /** |
||
1169 | * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression |
||
1170 | * |
||
1171 | * @return \Doctrine\ORM\Query\AST\SimpleSelectClause |
||
1172 | */ |
||
1173 | 49 | public function SimpleSelectClause() |
|
1174 | { |
||
1175 | 49 | $isDistinct = false; |
|
1176 | 49 | $this->match(Lexer::T_SELECT); |
|
1177 | |||
1178 | 49 | if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { |
|
1179 | $this->match(Lexer::T_DISTINCT); |
||
1180 | |||
1181 | $isDistinct = true; |
||
1182 | } |
||
1183 | |||
1184 | 49 | return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct); |
|
1185 | } |
||
1186 | |||
1187 | /** |
||
1188 | * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* |
||
1189 | * |
||
1190 | * @return \Doctrine\ORM\Query\AST\UpdateClause |
||
1191 | */ |
||
1192 | 32 | public function UpdateClause() |
|
1193 | { |
||
1194 | 32 | $this->match(Lexer::T_UPDATE); |
|
1195 | |||
1196 | 32 | $token = $this->lexer->lookahead; |
|
1197 | 32 | $abstractSchemaName = $this->AbstractSchemaName(); |
|
1198 | |||
1199 | 32 | $this->validateAbstractSchemaName($abstractSchemaName); |
|
1200 | |||
1201 | 32 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
1202 | 2 | $this->match(Lexer::T_AS); |
|
1203 | } |
||
1204 | |||
1205 | 32 | $aliasIdentificationVariable = $this->AliasIdentificationVariable(); |
|
1206 | |||
1207 | 32 | $class = $this->em->getClassMetadata($abstractSchemaName); |
|
1208 | |||
1209 | // Building queryComponent |
||
1210 | $queryComponent = [ |
||
1211 | 32 | 'metadata' => $class, |
|
1212 | 'parent' => null, |
||
1213 | 'relation' => null, |
||
1214 | 'map' => null, |
||
1215 | 32 | 'nestingLevel' => $this->nestingLevel, |
|
1216 | 32 | 'token' => $token, |
|
1217 | ]; |
||
1218 | |||
1219 | 32 | $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; |
|
1220 | |||
1221 | 32 | $this->match(Lexer::T_SET); |
|
1222 | |||
1223 | 32 | $updateItems = []; |
|
1224 | 32 | $updateItems[] = $this->UpdateItem(); |
|
1225 | |||
1226 | 32 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
1227 | 4 | $this->match(Lexer::T_COMMA); |
|
1228 | |||
1229 | 4 | $updateItems[] = $this->UpdateItem(); |
|
1230 | } |
||
1231 | |||
1232 | 32 | $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems); |
|
1233 | 32 | $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable; |
|
1234 | |||
1235 | 32 | return $updateClause; |
|
1236 | } |
||
1237 | |||
1238 | /** |
||
1239 | * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable |
||
1240 | * |
||
1241 | * @return \Doctrine\ORM\Query\AST\DeleteClause |
||
1242 | */ |
||
1243 | 41 | public function DeleteClause() |
|
1244 | { |
||
1245 | 41 | $this->match(Lexer::T_DELETE); |
|
1246 | |||
1247 | 41 | if ($this->lexer->isNextToken(Lexer::T_FROM)) { |
|
1248 | 8 | $this->match(Lexer::T_FROM); |
|
1249 | } |
||
1250 | |||
1251 | 41 | $token = $this->lexer->lookahead; |
|
1252 | 41 | $abstractSchemaName = $this->AbstractSchemaName(); |
|
1253 | |||
1254 | 41 | $this->validateAbstractSchemaName($abstractSchemaName); |
|
1255 | |||
1256 | 41 | $deleteClause = new AST\DeleteClause($abstractSchemaName); |
|
1257 | |||
1258 | 41 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
1259 | 1 | $this->match(Lexer::T_AS); |
|
1260 | } |
||
1261 | |||
1262 | 41 | $aliasIdentificationVariable = $this->AliasIdentificationVariable(); |
|
1263 | |||
1264 | 40 | $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable; |
|
1265 | 40 | $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName); |
|
1266 | |||
1267 | // Building queryComponent |
||
1268 | $queryComponent = [ |
||
1269 | 40 | 'metadata' => $class, |
|
1270 | 'parent' => null, |
||
1271 | 'relation' => null, |
||
1272 | 'map' => null, |
||
1273 | 40 | 'nestingLevel' => $this->nestingLevel, |
|
1274 | 40 | 'token' => $token, |
|
1275 | ]; |
||
1276 | |||
1277 | 40 | $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; |
|
1278 | |||
1279 | 40 | return $deleteClause; |
|
1280 | } |
||
1281 | |||
1282 | /** |
||
1283 | * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* |
||
1284 | * |
||
1285 | * @return \Doctrine\ORM\Query\AST\FromClause |
||
1286 | */ |
||
1287 | 774 | public function FromClause() |
|
1288 | { |
||
1289 | 774 | $this->match(Lexer::T_FROM); |
|
1290 | |||
1291 | 769 | $identificationVariableDeclarations = []; |
|
1292 | 769 | $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); |
|
1293 | |||
1294 | 749 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
1295 | 7 | $this->match(Lexer::T_COMMA); |
|
1296 | |||
1297 | 7 | $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration(); |
|
1298 | } |
||
1299 | |||
1300 | 749 | return new AST\FromClause($identificationVariableDeclarations); |
|
1301 | } |
||
1302 | |||
1303 | /** |
||
1304 | * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* |
||
1305 | * |
||
1306 | * @return \Doctrine\ORM\Query\AST\SubselectFromClause |
||
1307 | */ |
||
1308 | 49 | public function SubselectFromClause() |
|
1309 | { |
||
1310 | 49 | $this->match(Lexer::T_FROM); |
|
1311 | |||
1312 | 49 | $identificationVariables = []; |
|
1313 | 49 | $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); |
|
1314 | |||
1315 | 48 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
1316 | $this->match(Lexer::T_COMMA); |
||
1317 | |||
1318 | $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration(); |
||
1319 | } |
||
1320 | |||
1321 | 48 | return new AST\SubselectFromClause($identificationVariables); |
|
1322 | } |
||
1323 | |||
1324 | /** |
||
1325 | * WhereClause ::= "WHERE" ConditionalExpression |
||
1326 | * |
||
1327 | * @return \Doctrine\ORM\Query\AST\WhereClause |
||
1328 | */ |
||
1329 | 341 | public function WhereClause() |
|
1330 | { |
||
1331 | 341 | $this->match(Lexer::T_WHERE); |
|
1332 | |||
1333 | 341 | return new AST\WhereClause($this->ConditionalExpression()); |
|
1334 | } |
||
1335 | |||
1336 | /** |
||
1337 | * HavingClause ::= "HAVING" ConditionalExpression |
||
1338 | * |
||
1339 | * @return \Doctrine\ORM\Query\AST\HavingClause |
||
1340 | */ |
||
1341 | 21 | public function HavingClause() |
|
1342 | { |
||
1343 | 21 | $this->match(Lexer::T_HAVING); |
|
1344 | |||
1345 | 21 | return new AST\HavingClause($this->ConditionalExpression()); |
|
1346 | } |
||
1347 | |||
1348 | /** |
||
1349 | * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* |
||
1350 | * |
||
1351 | * @return \Doctrine\ORM\Query\AST\GroupByClause |
||
1352 | */ |
||
1353 | 33 | public function GroupByClause() |
|
1354 | { |
||
1355 | 33 | $this->match(Lexer::T_GROUP); |
|
1356 | 33 | $this->match(Lexer::T_BY); |
|
1357 | |||
1358 | 33 | $groupByItems = [$this->GroupByItem()]; |
|
1359 | |||
1360 | 32 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
1361 | 8 | $this->match(Lexer::T_COMMA); |
|
1362 | |||
1363 | 8 | $groupByItems[] = $this->GroupByItem(); |
|
1364 | } |
||
1365 | |||
1366 | 32 | return new AST\GroupByClause($groupByItems); |
|
1367 | } |
||
1368 | |||
1369 | /** |
||
1370 | * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* |
||
1371 | * |
||
1372 | * @return \Doctrine\ORM\Query\AST\OrderByClause |
||
1373 | */ |
||
1374 | 182 | public function OrderByClause() |
|
1375 | { |
||
1376 | 182 | $this->match(Lexer::T_ORDER); |
|
1377 | 182 | $this->match(Lexer::T_BY); |
|
1378 | |||
1379 | 182 | $orderByItems = []; |
|
1380 | 182 | $orderByItems[] = $this->OrderByItem(); |
|
1381 | |||
1382 | 182 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
1383 | 15 | $this->match(Lexer::T_COMMA); |
|
1384 | |||
1385 | 15 | $orderByItems[] = $this->OrderByItem(); |
|
1386 | } |
||
1387 | |||
1388 | 182 | return new AST\OrderByClause($orderByItems); |
|
1389 | } |
||
1390 | |||
1391 | /** |
||
1392 | * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] |
||
1393 | * |
||
1394 | * @return \Doctrine\ORM\Query\AST\Subselect |
||
1395 | */ |
||
1396 | 49 | public function Subselect() |
|
1397 | { |
||
1398 | // Increase query nesting level |
||
1399 | 49 | $this->nestingLevel++; |
|
1400 | |||
1401 | 49 | $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause()); |
|
1402 | |||
1403 | 48 | $subselect->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null; |
|
1404 | 48 | $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null; |
|
1405 | 48 | $subselect->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null; |
|
1406 | 48 | $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null; |
|
1407 | |||
1408 | // Decrease query nesting level |
||
1409 | 48 | $this->nestingLevel--; |
|
1410 | |||
1411 | 48 | return $subselect; |
|
1412 | } |
||
1413 | |||
1414 | /** |
||
1415 | * UpdateItem ::= SingleValuedPathExpression "=" NewValue |
||
1416 | * |
||
1417 | * @return \Doctrine\ORM\Query\AST\UpdateItem |
||
1418 | */ |
||
1419 | 32 | public function UpdateItem() |
|
1420 | { |
||
1421 | 32 | $pathExpr = $this->SingleValuedPathExpression(); |
|
1422 | |||
1423 | 32 | $this->match(Lexer::T_EQUALS); |
|
1424 | |||
1425 | 32 | $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue()); |
|
1426 | |||
1427 | 32 | return $updateItem; |
|
1428 | } |
||
1429 | |||
1430 | /** |
||
1431 | * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression |
||
1432 | * |
||
1433 | * @return string | \Doctrine\ORM\Query\AST\PathExpression |
||
1434 | */ |
||
1435 | 33 | public function GroupByItem() |
|
1436 | { |
||
1437 | // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression |
||
1438 | 33 | $glimpse = $this->lexer->glimpse(); |
|
1439 | |||
1440 | 33 | if ($glimpse['type'] === Lexer::T_DOT) { |
|
1441 | 14 | return $this->SingleValuedPathExpression(); |
|
1442 | } |
||
1443 | |||
1444 | // Still need to decide between IdentificationVariable or ResultVariable |
||
1445 | 19 | $lookaheadValue = $this->lexer->lookahead['value']; |
|
1446 | |||
1447 | 19 | if (! isset($this->queryComponents[$lookaheadValue])) { |
|
1448 | 1 | $this->semanticalError('Cannot group by undefined identification or result variable.'); |
|
1449 | } |
||
1450 | |||
1451 | 18 | return (isset($this->queryComponents[$lookaheadValue]['metadata'])) |
|
1452 | 16 | ? $this->IdentificationVariable() |
|
1453 | 18 | : $this->ResultVariable(); |
|
1454 | } |
||
1455 | |||
1456 | /** |
||
1457 | * OrderByItem ::= ( |
||
1458 | * SimpleArithmeticExpression | SingleValuedPathExpression | |
||
1459 | * ScalarExpression | ResultVariable | FunctionDeclaration |
||
1460 | * ) ["ASC" | "DESC"] |
||
1461 | * |
||
1462 | * @return \Doctrine\ORM\Query\AST\OrderByItem |
||
1463 | */ |
||
1464 | 182 | public function OrderByItem() |
|
1465 | { |
||
1466 | 182 | $this->lexer->peek(); // lookahead => '.' |
|
1467 | 182 | $this->lexer->peek(); // lookahead => token after '.' |
|
1468 | |||
1469 | 182 | $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' |
|
1470 | |||
1471 | 182 | $this->lexer->resetPeek(); |
|
1472 | |||
1473 | 182 | $glimpse = $this->lexer->glimpse(); |
|
1474 | |||
1475 | switch (true) { |
||
1476 | 182 | case ($this->isFunction()): |
|
1477 | 2 | $expr = $this->FunctionDeclaration(); |
|
1478 | 2 | break; |
|
1479 | |||
1480 | 180 | case ($this->isMathOperator($peek)): |
|
1481 | 25 | $expr = $this->SimpleArithmeticExpression(); |
|
1482 | 25 | break; |
|
1483 | |||
1484 | 156 | case ($glimpse['type'] === Lexer::T_DOT): |
|
1485 | 141 | $expr = $this->SingleValuedPathExpression(); |
|
1486 | 141 | break; |
|
1487 | |||
1488 | 19 | case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())): |
|
1489 | 2 | $expr = $this->ScalarExpression(); |
|
1490 | 2 | break; |
|
1491 | |||
1492 | default: |
||
1493 | 17 | $expr = $this->ResultVariable(); |
|
1494 | 17 | break; |
|
1495 | } |
||
1496 | |||
1497 | 182 | $type = 'ASC'; |
|
1498 | 182 | $item = new AST\OrderByItem($expr); |
|
1499 | |||
1500 | switch (true) { |
||
1501 | 182 | case ($this->lexer->isNextToken(Lexer::T_DESC)): |
|
1502 | 95 | $this->match(Lexer::T_DESC); |
|
1503 | 95 | $type = 'DESC'; |
|
1504 | 95 | break; |
|
1505 | |||
1506 | 154 | case ($this->lexer->isNextToken(Lexer::T_ASC)): |
|
1507 | 97 | $this->match(Lexer::T_ASC); |
|
1508 | 97 | break; |
|
1509 | |||
1510 | default: |
||
1511 | // Do nothing |
||
1512 | } |
||
1513 | |||
1514 | 182 | $item->type = $type; |
|
1515 | |||
1516 | 182 | return $item; |
|
1517 | } |
||
1518 | |||
1519 | /** |
||
1520 | * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary | |
||
1521 | * EnumPrimary | SimpleEntityExpression | "NULL" |
||
1522 | * |
||
1523 | * NOTE: Since it is not possible to correctly recognize individual types, here is the full |
||
1524 | * grammar that needs to be supported: |
||
1525 | * |
||
1526 | * NewValue ::= SimpleArithmeticExpression | "NULL" |
||
1527 | * |
||
1528 | * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression |
||
1529 | * |
||
1530 | * @return AST\ArithmeticExpression |
||
1531 | */ |
||
1532 | 32 | public function NewValue() |
|
1533 | { |
||
1534 | 32 | if ($this->lexer->isNextToken(Lexer::T_NULL)) { |
|
1535 | 1 | $this->match(Lexer::T_NULL); |
|
1536 | |||
1537 | 1 | return null; |
|
1538 | } |
||
1539 | |||
1540 | 31 | if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { |
|
1541 | 18 | $this->match(Lexer::T_INPUT_PARAMETER); |
|
1542 | |||
1543 | 18 | return new AST\InputParameter($this->lexer->token['value']); |
|
1544 | } |
||
1545 | |||
1546 | 13 | return $this->ArithmeticExpression(); |
|
1547 | } |
||
1548 | |||
1549 | /** |
||
1550 | * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}* |
||
1551 | * |
||
1552 | * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration |
||
1553 | */ |
||
1554 | 771 | public function IdentificationVariableDeclaration() |
|
1555 | { |
||
1556 | 771 | $joins = []; |
|
1557 | 771 | $rangeVariableDeclaration = $this->RangeVariableDeclaration(); |
|
1558 | 754 | $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) |
|
1559 | 7 | ? $this->IndexBy() |
|
1560 | 754 | : null; |
|
1561 | |||
1562 | 754 | $rangeVariableDeclaration->isRoot = true; |
|
1563 | |||
1564 | 754 | while ($this->lexer->isNextToken(Lexer::T_LEFT) || |
|
1565 | 754 | $this->lexer->isNextToken(Lexer::T_INNER) || |
|
1566 | 754 | $this->lexer->isNextToken(Lexer::T_JOIN) |
|
1567 | ) { |
||
1568 | 280 | $joins[] = $this->Join(); |
|
1569 | } |
||
1570 | |||
1571 | 751 | return new AST\IdentificationVariableDeclaration( |
|
1572 | 751 | $rangeVariableDeclaration, |
|
1573 | 751 | $indexBy, |
|
1574 | 751 | $joins |
|
1575 | ); |
||
1576 | } |
||
1577 | |||
1578 | /** |
||
1579 | * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration |
||
1580 | * |
||
1581 | * {Internal note: WARNING: Solution is harder than a bare implementation. |
||
1582 | * Desired EBNF support: |
||
1583 | * |
||
1584 | * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) |
||
1585 | * |
||
1586 | * It demands that entire SQL generation to become programmatical. This is |
||
1587 | * needed because association based subselect requires "WHERE" conditional |
||
1588 | * expressions to be injected, but there is no scope to do that. Only scope |
||
1589 | * accessible is "FROM", prohibiting an easy implementation without larger |
||
1590 | * changes.} |
||
1591 | * |
||
1592 | * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration | |
||
1593 | * \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration |
||
1594 | */ |
||
1595 | 49 | public function SubselectIdentificationVariableDeclaration() |
|
1596 | { |
||
1597 | /* |
||
1598 | NOT YET IMPLEMENTED! |
||
1599 | |||
1600 | $glimpse = $this->lexer->glimpse(); |
||
1601 | |||
1602 | if ($glimpse['type'] == Lexer::T_DOT) { |
||
1603 | $associationPathExpression = $this->AssociationPathExpression(); |
||
1604 | |||
1605 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
||
1606 | $this->match(Lexer::T_AS); |
||
1607 | } |
||
1608 | |||
1609 | $aliasIdentificationVariable = $this->AliasIdentificationVariable(); |
||
1610 | $identificationVariable = $associationPathExpression->identificationVariable; |
||
1611 | $field = $associationPathExpression->associationField; |
||
1612 | |||
1613 | $class = $this->queryComponents[$identificationVariable]['metadata']; |
||
1614 | $association = $class->getProperty($field); |
||
1615 | $targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
||
1616 | |||
1617 | // Building queryComponent |
||
1618 | $joinQueryComponent = array( |
||
1619 | 'metadata' => $targetClass, |
||
1620 | 'parent' => $identificationVariable, |
||
1621 | 'relation' => $association, |
||
1622 | 'map' => null, |
||
1623 | 'nestingLevel' => $this->nestingLevel, |
||
1624 | 'token' => $this->lexer->lookahead |
||
1625 | ); |
||
1626 | |||
1627 | $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; |
||
1628 | |||
1629 | return new AST\SubselectIdentificationVariableDeclaration( |
||
1630 | $associationPathExpression, $aliasIdentificationVariable |
||
1631 | ); |
||
1632 | } |
||
1633 | */ |
||
1634 | |||
1635 | 49 | return $this->IdentificationVariableDeclaration(); |
|
1636 | } |
||
1637 | |||
1638 | /** |
||
1639 | * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" |
||
1640 | * (JoinAssociationDeclaration | RangeVariableDeclaration) |
||
1641 | * ["WITH" ConditionalExpression] |
||
1642 | * |
||
1643 | * @return \Doctrine\ORM\Query\AST\Join |
||
1644 | */ |
||
1645 | 280 | public function Join() |
|
1646 | { |
||
1647 | // Check Join type |
||
1648 | 280 | $joinType = AST\Join::JOIN_TYPE_INNER; |
|
1649 | |||
1650 | switch (true) { |
||
1651 | 280 | case ($this->lexer->isNextToken(Lexer::T_LEFT)): |
|
1652 | 68 | $this->match(Lexer::T_LEFT); |
|
1653 | |||
1654 | 68 | $joinType = AST\Join::JOIN_TYPE_LEFT; |
|
1655 | |||
1656 | // Possible LEFT OUTER join |
||
1657 | 68 | if ($this->lexer->isNextToken(Lexer::T_OUTER)) { |
|
1658 | $this->match(Lexer::T_OUTER); |
||
1659 | |||
1660 | $joinType = AST\Join::JOIN_TYPE_LEFTOUTER; |
||
1661 | } |
||
1662 | 68 | break; |
|
1663 | |||
1664 | 215 | case ($this->lexer->isNextToken(Lexer::T_INNER)): |
|
1665 | 21 | $this->match(Lexer::T_INNER); |
|
1666 | 21 | break; |
|
1667 | |||
1668 | default: |
||
1669 | // Do nothing |
||
1670 | } |
||
1671 | |||
1672 | 280 | $this->match(Lexer::T_JOIN); |
|
1673 | |||
1674 | 280 | $next = $this->lexer->glimpse(); |
|
1675 | 280 | $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration(); |
|
1676 | 277 | $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH); |
|
1677 | 277 | $join = new AST\Join($joinType, $joinDeclaration); |
|
1678 | |||
1679 | // Describe non-root join declaration |
||
1680 | 277 | if ($joinDeclaration instanceof AST\RangeVariableDeclaration) { |
|
1681 | 22 | $joinDeclaration->isRoot = false; |
|
1682 | } |
||
1683 | |||
1684 | // Check for ad-hoc Join conditions |
||
1685 | 277 | if ($adhocConditions) { |
|
1686 | 25 | $this->match(Lexer::T_WITH); |
|
1687 | |||
1688 | 25 | $join->conditionalExpression = $this->ConditionalExpression(); |
|
1689 | } |
||
1690 | |||
1691 | 277 | return $join; |
|
1692 | } |
||
1693 | |||
1694 | /** |
||
1695 | * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable |
||
1696 | * |
||
1697 | * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration |
||
1698 | * |
||
1699 | * @throws QueryException |
||
1700 | */ |
||
1701 | 771 | public function RangeVariableDeclaration() |
|
1702 | { |
||
1703 | 771 | if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) { |
|
1704 | 2 | $this->semanticalError('Subquery is not supported here', $this->lexer->token); |
|
1705 | } |
||
1706 | |||
1707 | 770 | $abstractSchemaName = $this->AbstractSchemaName(); |
|
1708 | |||
1709 | 769 | $this->validateAbstractSchemaName($abstractSchemaName); |
|
1710 | |||
1711 | 754 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
1712 | 6 | $this->match(Lexer::T_AS); |
|
1713 | } |
||
1714 | |||
1715 | 754 | $token = $this->lexer->lookahead; |
|
1716 | 754 | $aliasIdentificationVariable = $this->AliasIdentificationVariable(); |
|
1717 | 754 | $classMetadata = $this->em->getClassMetadata($abstractSchemaName); |
|
1718 | |||
1719 | // Building queryComponent |
||
1720 | $queryComponent = [ |
||
1721 | 754 | 'metadata' => $classMetadata, |
|
1722 | 'parent' => null, |
||
1723 | 'relation' => null, |
||
1724 | 'map' => null, |
||
1725 | 754 | 'nestingLevel' => $this->nestingLevel, |
|
1726 | 754 | 'token' => $token, |
|
1727 | ]; |
||
1728 | |||
1729 | 754 | $this->queryComponents[$aliasIdentificationVariable] = $queryComponent; |
|
1730 | |||
1731 | 754 | return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable); |
|
1732 | } |
||
1733 | |||
1734 | /** |
||
1735 | * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy] |
||
1736 | * |
||
1737 | * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression |
||
1738 | */ |
||
1739 | 258 | public function JoinAssociationDeclaration() |
|
1740 | { |
||
1741 | 258 | $joinAssociationPathExpression = $this->JoinAssociationPathExpression(); |
|
1742 | |||
1743 | 258 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
1744 | 5 | $this->match(Lexer::T_AS); |
|
1745 | } |
||
1746 | |||
1747 | 258 | $aliasIdentificationVariable = $this->AliasIdentificationVariable(); |
|
1748 | 256 | $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null; |
|
1749 | |||
1750 | 256 | $identificationVariable = $joinAssociationPathExpression->identificationVariable; |
|
1751 | 256 | $field = $joinAssociationPathExpression->associationField; |
|
1752 | |||
1753 | 256 | $class = $this->queryComponents[$identificationVariable]['metadata']; |
|
1754 | 256 | $association = $class->getProperty($field); |
|
1755 | 256 | $targetClass = $this->em->getClassMetadata($association->getTargetEntity()); |
|
1756 | |||
1757 | // Building queryComponent |
||
1758 | $joinQueryComponent = [ |
||
1759 | 256 | 'metadata' => $targetClass, |
|
1760 | 256 | 'parent' => $joinAssociationPathExpression->identificationVariable, |
|
1761 | 256 | 'relation' => $association, |
|
1762 | 'map' => null, |
||
1763 | 256 | 'nestingLevel' => $this->nestingLevel, |
|
1764 | 256 | 'token' => $this->lexer->lookahead, |
|
1765 | ]; |
||
1766 | |||
1767 | 256 | $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent; |
|
1768 | |||
1769 | 256 | return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy); |
|
1770 | } |
||
1771 | |||
1772 | /** |
||
1773 | * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet |
||
1774 | * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" |
||
1775 | * |
||
1776 | * @return \Doctrine\ORM\Query\AST\PartialObjectExpression |
||
1777 | */ |
||
1778 | 9 | public function PartialObjectExpression() |
|
1779 | { |
||
1780 | 9 | $this->match(Lexer::T_PARTIAL); |
|
1781 | |||
1782 | 9 | $partialFieldSet = []; |
|
1783 | |||
1784 | 9 | $identificationVariable = $this->IdentificationVariable(); |
|
1785 | |||
1786 | 9 | $this->match(Lexer::T_DOT); |
|
1787 | 9 | $this->match(Lexer::T_OPEN_CURLY_BRACE); |
|
1788 | 9 | $this->match(Lexer::T_IDENTIFIER); |
|
1789 | |||
1790 | 9 | $field = $this->lexer->token['value']; |
|
1791 | |||
1792 | // First field in partial expression might be embeddable property |
||
1793 | 9 | while ($this->lexer->isNextToken(Lexer::T_DOT)) { |
|
1794 | $this->match(Lexer::T_DOT); |
||
1795 | $this->match(Lexer::T_IDENTIFIER); |
||
1796 | $field .= '.' . $this->lexer->token['value']; |
||
1797 | } |
||
1798 | |||
1799 | 9 | $partialFieldSet[] = $field; |
|
1800 | |||
1801 | 9 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
1802 | 7 | $this->match(Lexer::T_COMMA); |
|
1803 | 7 | $this->match(Lexer::T_IDENTIFIER); |
|
1804 | |||
1805 | 7 | $field = $this->lexer->token['value']; |
|
1806 | |||
1807 | 7 | while ($this->lexer->isNextToken(Lexer::T_DOT)) { |
|
1808 | $this->match(Lexer::T_DOT); |
||
1809 | $this->match(Lexer::T_IDENTIFIER); |
||
1810 | $field .= '.' . $this->lexer->token['value']; |
||
1811 | } |
||
1812 | |||
1813 | 7 | $partialFieldSet[] = $field; |
|
1814 | } |
||
1815 | |||
1816 | 9 | $this->match(Lexer::T_CLOSE_CURLY_BRACE); |
|
1817 | |||
1818 | 9 | $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet); |
|
1819 | |||
1820 | // Defer PartialObjectExpression validation |
||
1821 | 9 | $this->deferredPartialObjectExpressions[] = [ |
|
1822 | 9 | 'expression' => $partialObjectExpression, |
|
1823 | 9 | 'nestingLevel' => $this->nestingLevel, |
|
1824 | 9 | 'token' => $this->lexer->token, |
|
1825 | ]; |
||
1826 | |||
1827 | 9 | return $partialObjectExpression; |
|
1828 | } |
||
1829 | |||
1830 | /** |
||
1831 | * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")" |
||
1832 | * |
||
1833 | * @return \Doctrine\ORM\Query\AST\NewObjectExpression |
||
1834 | */ |
||
1835 | 28 | public function NewObjectExpression() |
|
1836 | { |
||
1837 | 28 | $this->match(Lexer::T_NEW); |
|
1838 | |||
1839 | 28 | $className = $this->AbstractSchemaName(); // note that this is not yet validated |
|
1840 | 28 | $token = $this->lexer->token; |
|
1841 | |||
1842 | 28 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
1843 | |||
1844 | 28 | $args[] = $this->NewObjectArg(); |
|
1845 | |||
1846 | 28 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
1847 | 24 | $this->match(Lexer::T_COMMA); |
|
1848 | |||
1849 | 24 | $args[] = $this->NewObjectArg(); |
|
1850 | } |
||
1851 | |||
1852 | 28 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
1853 | |||
1854 | 28 | $expression = new AST\NewObjectExpression($className, $args); |
|
1855 | |||
1856 | // Defer NewObjectExpression validation |
||
1857 | 28 | $this->deferredNewObjectExpressions[] = [ |
|
1858 | 28 | 'token' => $token, |
|
1859 | 28 | 'expression' => $expression, |
|
1860 | 28 | 'nestingLevel' => $this->nestingLevel, |
|
1861 | ]; |
||
1862 | |||
1863 | 28 | return $expression; |
|
1864 | } |
||
1865 | |||
1866 | /** |
||
1867 | * NewObjectArg ::= ScalarExpression | "(" Subselect ")" |
||
1868 | * |
||
1869 | * @return mixed |
||
1870 | */ |
||
1871 | 28 | public function NewObjectArg() |
|
1872 | { |
||
1873 | 28 | $token = $this->lexer->lookahead; |
|
1874 | 28 | $peek = $this->lexer->glimpse(); |
|
1875 | |||
1876 | 28 | if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) { |
|
1877 | 2 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
1878 | 2 | $expression = $this->Subselect(); |
|
1879 | 2 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
1880 | |||
1881 | 2 | return $expression; |
|
1882 | } |
||
1883 | |||
1884 | 28 | return $this->ScalarExpression(); |
|
1885 | } |
||
1886 | |||
1887 | /** |
||
1888 | * IndexBy ::= "INDEX" "BY" StateFieldPathExpression |
||
1889 | * |
||
1890 | * @return \Doctrine\ORM\Query\AST\IndexBy |
||
1891 | */ |
||
1892 | 11 | public function IndexBy() |
|
1893 | { |
||
1894 | 11 | $this->match(Lexer::T_INDEX); |
|
1895 | 11 | $this->match(Lexer::T_BY); |
|
1896 | 11 | $pathExpr = $this->StateFieldPathExpression(); |
|
1897 | |||
1898 | // Add the INDEX BY info to the query component |
||
1899 | 11 | $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field; |
|
1900 | |||
1901 | 11 | return new AST\IndexBy($pathExpr); |
|
1902 | } |
||
1903 | |||
1904 | /** |
||
1905 | * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | |
||
1906 | * StateFieldPathExpression | BooleanPrimary | CaseExpression | |
||
1907 | * InstanceOfExpression |
||
1908 | * |
||
1909 | * @return mixed One of the possible expressions or subexpressions. |
||
1910 | */ |
||
1911 | 165 | public function ScalarExpression() |
|
1912 | { |
||
1913 | 165 | $lookahead = $this->lexer->lookahead['type']; |
|
1914 | 165 | $peek = $this->lexer->glimpse(); |
|
1915 | |||
1916 | 165 | switch (true) { |
|
1917 | case ($lookahead === Lexer::T_INTEGER): |
||
1918 | 162 | case ($lookahead === Lexer::T_FLOAT): |
|
1919 | // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) or ( - 1 ) or ( + 1 ) |
||
1920 | 162 | case ($lookahead === Lexer::T_MINUS): |
|
1921 | 162 | case ($lookahead === Lexer::T_PLUS): |
|
1922 | 17 | return $this->SimpleArithmeticExpression(); |
|
1923 | |||
1924 | 162 | case ($lookahead === Lexer::T_STRING): |
|
1925 | 13 | return $this->StringPrimary(); |
|
1926 | |||
1927 | 160 | case ($lookahead === Lexer::T_TRUE): |
|
1928 | 160 | case ($lookahead === Lexer::T_FALSE): |
|
1929 | 3 | $this->match($lookahead); |
|
1930 | |||
1931 | 3 | return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']); |
|
1932 | |||
1933 | 160 | case ($lookahead === Lexer::T_INPUT_PARAMETER): |
|
1934 | switch (true) { |
||
1935 | 1 | case $this->isMathOperator($peek): |
|
1936 | // :param + u.value |
||
1937 | 1 | return $this->SimpleArithmeticExpression(); |
|
1938 | default: |
||
1939 | return $this->InputParameter(); |
||
1940 | } |
||
1941 | // cannot get here |
||
1942 | |||
1943 | 160 | case ($lookahead === Lexer::T_CASE): |
|
1944 | 156 | case ($lookahead === Lexer::T_COALESCE): |
|
1945 | 156 | case ($lookahead === Lexer::T_NULLIF): |
|
1946 | // Since NULLIF and COALESCE can be identified as a function, |
||
1947 | // we need to check these before checking for FunctionDeclaration |
||
1948 | 8 | return $this->CaseExpression(); |
|
1949 | |||
1950 | 156 | case ($lookahead === Lexer::T_OPEN_PARENTHESIS): |
|
1951 | 4 | return $this->SimpleArithmeticExpression(); |
|
1952 | |||
1953 | // this check must be done before checking for a filed path expression |
||
1954 | 153 | case ($this->isFunction()): |
|
1955 | 27 | $this->lexer->peek(); // "(" |
|
1956 | |||
1957 | switch (true) { |
||
1958 | 27 | case ($this->isMathOperator($this->peekBeyondClosingParenthesis())): |
|
1959 | // SUM(u.id) + COUNT(u.id) |
||
1960 | 7 | return $this->SimpleArithmeticExpression(); |
|
1961 | |||
1962 | default: |
||
1963 | // IDENTITY(u) |
||
1964 | 22 | return $this->FunctionDeclaration(); |
|
1965 | } |
||
1966 | |||
1967 | break; |
||
1968 | // it is no function, so it must be a field path |
||
1969 | 134 | case ($lookahead === Lexer::T_IDENTIFIER): |
|
1970 | 134 | $this->lexer->peek(); // lookahead => '.' |
|
1971 | 134 | $this->lexer->peek(); // lookahead => token after '.' |
|
1972 | 134 | $peek = $this->lexer->peek(); // lookahead => token after the token after the '.' |
|
1973 | 134 | $this->lexer->resetPeek(); |
|
1974 | |||
1975 | 134 | if ($this->isMathOperator($peek)) { |
|
1976 | 7 | return $this->SimpleArithmeticExpression(); |
|
1977 | } |
||
1978 | |||
1979 | 129 | return $this->StateFieldPathExpression(); |
|
1980 | |||
1981 | default: |
||
1982 | $this->syntaxError(); |
||
1983 | } |
||
1984 | } |
||
1985 | |||
1986 | /** |
||
1987 | * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression |
||
1988 | * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" |
||
1989 | * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression |
||
1990 | * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" |
||
1991 | * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator |
||
1992 | * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression |
||
1993 | * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" |
||
1994 | * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" |
||
1995 | * |
||
1996 | * @return mixed One of the possible expressions or subexpressions. |
||
1997 | */ |
||
1998 | 19 | public function CaseExpression() |
|
1999 | { |
||
2000 | 19 | $lookahead = $this->lexer->lookahead['type']; |
|
2001 | |||
2002 | 19 | switch ($lookahead) { |
|
2003 | case Lexer::T_NULLIF: |
||
2004 | 5 | return $this->NullIfExpression(); |
|
2005 | |||
2006 | case Lexer::T_COALESCE: |
||
2007 | 2 | return $this->CoalesceExpression(); |
|
2008 | |||
2009 | case Lexer::T_CASE: |
||
2010 | 14 | $this->lexer->resetPeek(); |
|
2011 | 14 | $peek = $this->lexer->peek(); |
|
2012 | |||
2013 | 14 | if ($peek['type'] === Lexer::T_WHEN) { |
|
2014 | 9 | return $this->GeneralCaseExpression(); |
|
2015 | } |
||
2016 | |||
2017 | 5 | return $this->SimpleCaseExpression(); |
|
2018 | |||
2019 | default: |
||
2020 | // Do nothing |
||
2021 | break; |
||
2022 | } |
||
2023 | |||
2024 | $this->syntaxError(); |
||
2025 | } |
||
2026 | |||
2027 | /** |
||
2028 | * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" |
||
2029 | * |
||
2030 | * @return \Doctrine\ORM\Query\AST\CoalesceExpression |
||
2031 | */ |
||
2032 | 3 | public function CoalesceExpression() |
|
2033 | { |
||
2034 | 3 | $this->match(Lexer::T_COALESCE); |
|
2035 | 3 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2036 | |||
2037 | // Process ScalarExpressions (1..N) |
||
2038 | 3 | $scalarExpressions = []; |
|
2039 | 3 | $scalarExpressions[] = $this->ScalarExpression(); |
|
2040 | |||
2041 | 3 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
2042 | 3 | $this->match(Lexer::T_COMMA); |
|
2043 | |||
2044 | 3 | $scalarExpressions[] = $this->ScalarExpression(); |
|
2045 | } |
||
2046 | |||
2047 | 3 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2048 | |||
2049 | 3 | return new AST\CoalesceExpression($scalarExpressions); |
|
2050 | } |
||
2051 | |||
2052 | /** |
||
2053 | * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" |
||
2054 | * |
||
2055 | * @return \Doctrine\ORM\Query\AST\NullIfExpression |
||
2056 | */ |
||
2057 | 5 | public function NullIfExpression() |
|
2058 | { |
||
2059 | 5 | $this->match(Lexer::T_NULLIF); |
|
2060 | 5 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2061 | |||
2062 | 5 | $firstExpression = $this->ScalarExpression(); |
|
2063 | 5 | $this->match(Lexer::T_COMMA); |
|
2064 | 5 | $secondExpression = $this->ScalarExpression(); |
|
2065 | |||
2066 | 5 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2067 | |||
2068 | 5 | return new AST\NullIfExpression($firstExpression, $secondExpression); |
|
2069 | } |
||
2070 | |||
2071 | /** |
||
2072 | * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" |
||
2073 | * |
||
2074 | * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression |
||
2075 | */ |
||
2076 | 9 | public function GeneralCaseExpression() |
|
2077 | { |
||
2078 | 9 | $this->match(Lexer::T_CASE); |
|
2079 | |||
2080 | // Process WhenClause (1..N) |
||
2081 | 9 | $whenClauses = []; |
|
2082 | |||
2083 | do { |
||
2084 | 9 | $whenClauses[] = $this->WhenClause(); |
|
2085 | 9 | } while ($this->lexer->isNextToken(Lexer::T_WHEN)); |
|
2086 | |||
2087 | 9 | $this->match(Lexer::T_ELSE); |
|
2088 | 9 | $scalarExpression = $this->ScalarExpression(); |
|
2089 | 9 | $this->match(Lexer::T_END); |
|
2090 | |||
2091 | 9 | return new AST\GeneralCaseExpression($whenClauses, $scalarExpression); |
|
2092 | } |
||
2093 | |||
2094 | /** |
||
2095 | * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" |
||
2096 | * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator |
||
2097 | * |
||
2098 | * @return AST\SimpleCaseExpression |
||
2099 | */ |
||
2100 | 5 | public function SimpleCaseExpression() |
|
2101 | { |
||
2102 | 5 | $this->match(Lexer::T_CASE); |
|
2103 | 5 | $caseOperand = $this->StateFieldPathExpression(); |
|
2104 | |||
2105 | // Process SimpleWhenClause (1..N) |
||
2106 | 5 | $simpleWhenClauses = []; |
|
2107 | |||
2108 | do { |
||
2109 | 5 | $simpleWhenClauses[] = $this->SimpleWhenClause(); |
|
2110 | 5 | } while ($this->lexer->isNextToken(Lexer::T_WHEN)); |
|
2111 | |||
2112 | 5 | $this->match(Lexer::T_ELSE); |
|
2113 | 5 | $scalarExpression = $this->ScalarExpression(); |
|
2114 | 5 | $this->match(Lexer::T_END); |
|
2115 | |||
2116 | 5 | return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression); |
|
2117 | } |
||
2118 | |||
2119 | /** |
||
2120 | * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression |
||
2121 | * |
||
2122 | * @return \Doctrine\ORM\Query\AST\WhenClause |
||
2123 | */ |
||
2124 | 9 | public function WhenClause() |
|
2125 | { |
||
2126 | 9 | $this->match(Lexer::T_WHEN); |
|
2127 | 9 | $conditionalExpression = $this->ConditionalExpression(); |
|
2128 | 9 | $this->match(Lexer::T_THEN); |
|
2129 | |||
2130 | 9 | return new AST\WhenClause($conditionalExpression, $this->ScalarExpression()); |
|
2131 | } |
||
2132 | |||
2133 | /** |
||
2134 | * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression |
||
2135 | * |
||
2136 | * @return \Doctrine\ORM\Query\AST\SimpleWhenClause |
||
2137 | */ |
||
2138 | 5 | public function SimpleWhenClause() |
|
2139 | { |
||
2140 | 5 | $this->match(Lexer::T_WHEN); |
|
2141 | 5 | $conditionalExpression = $this->ScalarExpression(); |
|
2142 | 5 | $this->match(Lexer::T_THEN); |
|
2143 | |||
2144 | 5 | return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression()); |
|
2145 | } |
||
2146 | |||
2147 | /** |
||
2148 | * SelectExpression ::= ( |
||
2149 | * IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | |
||
2150 | * PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression |
||
2151 | * ) [["AS"] ["HIDDEN"] AliasResultVariable] |
||
2152 | * |
||
2153 | * @return \Doctrine\ORM\Query\AST\SelectExpression |
||
2154 | */ |
||
2155 | 783 | public function SelectExpression() |
|
2156 | { |
||
2157 | 783 | $expression = null; |
|
2158 | 783 | $identVariable = null; |
|
2159 | 783 | $peek = $this->lexer->glimpse(); |
|
2160 | 783 | $lookaheadType = $this->lexer->lookahead['type']; |
|
2161 | |||
2162 | 783 | switch (true) { |
|
2163 | // ScalarExpression (u.name) |
||
2164 | 705 | case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT): |
|
2165 | 106 | $expression = $this->ScalarExpression(); |
|
2166 | 106 | break; |
|
2167 | |||
2168 | // IdentificationVariable (u) |
||
2169 | 723 | case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS): |
|
2170 | 596 | $expression = $identVariable = $this->IdentificationVariable(); |
|
2171 | 596 | break; |
|
2172 | |||
2173 | // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...)) |
||
2174 | 189 | case ($lookaheadType === Lexer::T_CASE): |
|
2175 | 184 | case ($lookaheadType === Lexer::T_COALESCE): |
|
2176 | 182 | case ($lookaheadType === Lexer::T_NULLIF): |
|
2177 | 9 | $expression = $this->CaseExpression(); |
|
2178 | 9 | break; |
|
2179 | |||
2180 | // DQL Function (SUM(u.value) or SUM(u.value) + 1) |
||
2181 | 180 | case ($this->isFunction()): |
|
2182 | 102 | $this->lexer->peek(); // "(" |
|
2183 | |||
2184 | switch (true) { |
||
2185 | 102 | case ($this->isMathOperator($this->peekBeyondClosingParenthesis())): |
|
2186 | // SUM(u.id) + COUNT(u.id) |
||
2187 | 2 | $expression = $this->ScalarExpression(); |
|
2188 | 2 | break; |
|
2189 | |||
2190 | default: |
||
2191 | // IDENTITY(u) |
||
2192 | 100 | $expression = $this->FunctionDeclaration(); |
|
2193 | 100 | break; |
|
2194 | } |
||
2195 | |||
2196 | 102 | break; |
|
2197 | |||
2198 | // PartialObjectExpression (PARTIAL u.{id, name}) |
||
2199 | 79 | case ($lookaheadType === Lexer::T_PARTIAL): |
|
2200 | 9 | $expression = $this->PartialObjectExpression(); |
|
2201 | 9 | $identVariable = $expression->identificationVariable; |
|
2202 | 9 | break; |
|
2203 | |||
2204 | // Subselect |
||
2205 | 70 | case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT): |
|
2206 | 23 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2207 | 23 | $expression = $this->Subselect(); |
|
2208 | 23 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2209 | 23 | break; |
|
2210 | |||
2211 | // Shortcut: ScalarExpression => SimpleArithmeticExpression |
||
2212 | 47 | case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS): |
|
2213 | 43 | case ($lookaheadType === Lexer::T_INTEGER): |
|
2214 | 41 | case ($lookaheadType === Lexer::T_STRING): |
|
2215 | 32 | case ($lookaheadType === Lexer::T_FLOAT): |
|
2216 | // SimpleArithmeticExpression : (- u.value ) or ( + u.value ) |
||
2217 | 32 | case ($lookaheadType === Lexer::T_MINUS): |
|
2218 | 32 | case ($lookaheadType === Lexer::T_PLUS): |
|
2219 | 16 | $expression = $this->SimpleArithmeticExpression(); |
|
2220 | 16 | break; |
|
2221 | |||
2222 | // NewObjectExpression (New ClassName(id, name)) |
||
2223 | 31 | case ($lookaheadType === Lexer::T_NEW): |
|
2224 | 28 | $expression = $this->NewObjectExpression(); |
|
2225 | 28 | break; |
|
2226 | |||
2227 | default: |
||
2228 | 3 | $this->syntaxError( |
|
2229 | 3 | 'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression', |
|
2230 | 3 | $this->lexer->lookahead |
|
2231 | ); |
||
2232 | } |
||
2233 | |||
2234 | // [["AS"] ["HIDDEN"] AliasResultVariable] |
||
2235 | 780 | $mustHaveAliasResultVariable = false; |
|
2236 | |||
2237 | 780 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
2238 | 121 | $this->match(Lexer::T_AS); |
|
2239 | |||
2240 | 121 | $mustHaveAliasResultVariable = true; |
|
2241 | } |
||
2242 | |||
2243 | 780 | $hiddenAliasResultVariable = false; |
|
2244 | |||
2245 | 780 | if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) { |
|
2246 | 10 | $this->match(Lexer::T_HIDDEN); |
|
2247 | |||
2248 | 10 | $hiddenAliasResultVariable = true; |
|
2249 | } |
||
2250 | |||
2251 | 780 | $aliasResultVariable = null; |
|
2252 | |||
2253 | 780 | if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { |
|
2254 | 130 | $token = $this->lexer->lookahead; |
|
2255 | 130 | $aliasResultVariable = $this->AliasResultVariable(); |
|
2256 | |||
2257 | // Include AliasResultVariable in query components. |
||
2258 | 125 | $this->queryComponents[$aliasResultVariable] = [ |
|
2259 | 125 | 'resultVariable' => $expression, |
|
2260 | 125 | 'nestingLevel' => $this->nestingLevel, |
|
2261 | 125 | 'token' => $token, |
|
2262 | ]; |
||
2263 | } |
||
2264 | |||
2265 | // AST |
||
2266 | |||
2267 | 775 | $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable); |
|
2268 | |||
2269 | 775 | if ($identVariable) { |
|
2270 | 603 | $this->identVariableExpressions[$identVariable] = $expr; |
|
2271 | } |
||
2272 | |||
2273 | 775 | return $expr; |
|
2274 | } |
||
2275 | |||
2276 | /** |
||
2277 | * SimpleSelectExpression ::= ( |
||
2278 | * StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | |
||
2279 | * AggregateExpression | "(" Subselect ")" | ScalarExpression |
||
2280 | * ) [["AS"] AliasResultVariable] |
||
2281 | * |
||
2282 | * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression |
||
2283 | */ |
||
2284 | 49 | public function SimpleSelectExpression() |
|
2285 | { |
||
2286 | 49 | $peek = $this->lexer->glimpse(); |
|
2287 | |||
2288 | 49 | switch ($this->lexer->lookahead['type']) { |
|
2289 | case Lexer::T_IDENTIFIER: |
||
2290 | 19 | switch (true) { |
|
2291 | 19 | case ($peek['type'] === Lexer::T_DOT): |
|
2292 | 16 | $expression = $this->StateFieldPathExpression(); |
|
2293 | |||
2294 | 16 | return new AST\SimpleSelectExpression($expression); |
|
2295 | |||
2296 | 3 | case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS): |
|
2297 | 2 | $expression = $this->IdentificationVariable(); |
|
2298 | |||
2299 | 2 | return new AST\SimpleSelectExpression($expression); |
|
2300 | |||
2301 | 1 | case ($this->isFunction()): |
|
2302 | // SUM(u.id) + COUNT(u.id) |
||
2303 | 1 | if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) { |
|
2304 | return new AST\SimpleSelectExpression($this->ScalarExpression()); |
||
2305 | } |
||
2306 | // COUNT(u.id) |
||
2307 | 1 | if ($this->isAggregateFunction($this->lexer->lookahead['type'])) { |
|
2308 | return new AST\SimpleSelectExpression($this->AggregateExpression()); |
||
2309 | } |
||
2310 | // IDENTITY(u) |
||
2311 | 1 | return new AST\SimpleSelectExpression($this->FunctionDeclaration()); |
|
2312 | |||
2313 | default: |
||
2314 | // Do nothing |
||
2315 | } |
||
2316 | break; |
||
2317 | |||
2318 | case Lexer::T_OPEN_PARENTHESIS: |
||
2319 | 3 | if ($peek['type'] !== Lexer::T_SELECT) { |
|
2320 | // Shortcut: ScalarExpression => SimpleArithmeticExpression |
||
2321 | 3 | $expression = $this->SimpleArithmeticExpression(); |
|
2322 | |||
2323 | 3 | return new AST\SimpleSelectExpression($expression); |
|
2324 | } |
||
2325 | |||
2326 | // Subselect |
||
2327 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
||
2328 | $expression = $this->Subselect(); |
||
2329 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
||
2330 | |||
2331 | return new AST\SimpleSelectExpression($expression); |
||
2332 | |||
2333 | default: |
||
2334 | // Do nothing |
||
2335 | } |
||
2336 | |||
2337 | 28 | $this->lexer->peek(); |
|
2338 | |||
2339 | 28 | $expression = $this->ScalarExpression(); |
|
2340 | 28 | $expr = new AST\SimpleSelectExpression($expression); |
|
2341 | |||
2342 | 28 | if ($this->lexer->isNextToken(Lexer::T_AS)) { |
|
2343 | 1 | $this->match(Lexer::T_AS); |
|
2344 | } |
||
2345 | |||
2346 | 28 | if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) { |
|
2347 | 2 | $token = $this->lexer->lookahead; |
|
2348 | 2 | $resultVariable = $this->AliasResultVariable(); |
|
2349 | 2 | $expr->fieldIdentificationVariable = $resultVariable; |
|
2350 | |||
2351 | // Include AliasResultVariable in query components. |
||
2352 | 2 | $this->queryComponents[$resultVariable] = [ |
|
2353 | 2 | 'resultvariable' => $expr, |
|
2354 | 2 | 'nestingLevel' => $this->nestingLevel, |
|
2355 | 2 | 'token' => $token, |
|
2356 | ]; |
||
2357 | } |
||
2358 | |||
2359 | 28 | return $expr; |
|
2360 | } |
||
2361 | |||
2362 | /** |
||
2363 | * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* |
||
2364 | * |
||
2365 | * @return \Doctrine\ORM\Query\AST\ConditionalExpression |
||
2366 | */ |
||
2367 | 385 | public function ConditionalExpression() |
|
2368 | { |
||
2369 | 385 | $conditionalTerms = []; |
|
2370 | 385 | $conditionalTerms[] = $this->ConditionalTerm(); |
|
2371 | |||
2372 | 382 | while ($this->lexer->isNextToken(Lexer::T_OR)) { |
|
2373 | 16 | $this->match(Lexer::T_OR); |
|
2374 | |||
2375 | 16 | $conditionalTerms[] = $this->ConditionalTerm(); |
|
2376 | } |
||
2377 | |||
2378 | // Phase 1 AST optimization: Prevent AST\ConditionalExpression |
||
2379 | // if only one AST\ConditionalTerm is defined |
||
2380 | 382 | if (\count($conditionalTerms) === 1) { |
|
2381 | 374 | return $conditionalTerms[0]; |
|
2382 | } |
||
2383 | |||
2384 | 16 | return new AST\ConditionalExpression($conditionalTerms); |
|
2385 | } |
||
2386 | |||
2387 | /** |
||
2388 | * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* |
||
2389 | * |
||
2390 | * @return \Doctrine\ORM\Query\AST\ConditionalTerm |
||
2391 | */ |
||
2392 | 385 | public function ConditionalTerm() |
|
2393 | { |
||
2394 | 385 | $conditionalFactors = []; |
|
2395 | 385 | $conditionalFactors[] = $this->ConditionalFactor(); |
|
2396 | |||
2397 | 382 | while ($this->lexer->isNextToken(Lexer::T_AND)) { |
|
2398 | 32 | $this->match(Lexer::T_AND); |
|
2399 | |||
2400 | 32 | $conditionalFactors[] = $this->ConditionalFactor(); |
|
2401 | } |
||
2402 | |||
2403 | // Phase 1 AST optimization: Prevent AST\ConditionalTerm |
||
2404 | // if only one AST\ConditionalFactor is defined |
||
2405 | 382 | if (\count($conditionalFactors) === 1) { |
|
2406 | 363 | return $conditionalFactors[0]; |
|
2407 | } |
||
2408 | |||
2409 | 32 | return new AST\ConditionalTerm($conditionalFactors); |
|
2410 | } |
||
2411 | |||
2412 | /** |
||
2413 | * ConditionalFactor ::= ["NOT"] ConditionalPrimary |
||
2414 | * |
||
2415 | * @return \Doctrine\ORM\Query\AST\ConditionalFactor |
||
2416 | */ |
||
2417 | 385 | public function ConditionalFactor() |
|
2418 | { |
||
2419 | 385 | $not = false; |
|
2420 | |||
2421 | 385 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
2422 | 6 | $this->match(Lexer::T_NOT); |
|
2423 | |||
2424 | 6 | $not = true; |
|
2425 | } |
||
2426 | |||
2427 | 385 | $conditionalPrimary = $this->ConditionalPrimary(); |
|
2428 | |||
2429 | // Phase 1 AST optimization: Prevent AST\ConditionalFactor |
||
2430 | // if only one AST\ConditionalPrimary is defined |
||
2431 | 382 | if (! $not) { |
|
2432 | 380 | return $conditionalPrimary; |
|
2433 | } |
||
2434 | |||
2435 | 6 | $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary); |
|
2436 | 6 | $conditionalFactor->not = $not; |
|
2437 | |||
2438 | 6 | return $conditionalFactor; |
|
2439 | } |
||
2440 | |||
2441 | /** |
||
2442 | * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" |
||
2443 | * |
||
2444 | * @return \Doctrine\ORM\Query\AST\ConditionalPrimary |
||
2445 | */ |
||
2446 | 385 | public function ConditionalPrimary() |
|
2447 | { |
||
2448 | 385 | $condPrimary = new AST\ConditionalPrimary; |
|
2449 | |||
2450 | 385 | if (! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { |
|
2451 | 376 | $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); |
|
2452 | |||
2453 | 373 | return $condPrimary; |
|
2454 | } |
||
2455 | |||
2456 | // Peek beyond the matching closing parenthesis ')' |
||
2457 | 25 | $peek = $this->peekBeyondClosingParenthesis(); |
|
2458 | |||
2459 | 25 | if (in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!=']) || |
|
2460 | 22 | in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) || |
|
2461 | 25 | $this->isMathOperator($peek)) { |
|
2462 | 15 | $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression(); |
|
2463 | |||
2464 | 15 | return $condPrimary; |
|
2465 | } |
||
2466 | |||
2467 | 21 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2468 | 21 | $condPrimary->conditionalExpression = $this->ConditionalExpression(); |
|
2469 | 21 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2470 | |||
2471 | 21 | return $condPrimary; |
|
2472 | } |
||
2473 | |||
2474 | /** |
||
2475 | * SimpleConditionalExpression ::= |
||
2476 | * ComparisonExpression | BetweenExpression | LikeExpression | |
||
2477 | * InExpression | NullComparisonExpression | ExistsExpression | |
||
2478 | * EmptyCollectionComparisonExpression | CollectionMemberExpression | |
||
2479 | * InstanceOfExpression |
||
2480 | */ |
||
2481 | 385 | public function SimpleConditionalExpression() |
|
2482 | { |
||
2483 | 385 | if ($this->lexer->isNextToken(Lexer::T_EXISTS)) { |
|
2484 | 7 | return $this->ExistsExpression(); |
|
2485 | } |
||
2486 | |||
2487 | 385 | $token = $this->lexer->lookahead; |
|
2488 | 385 | $peek = $this->lexer->glimpse(); |
|
2489 | 385 | $lookahead = $token; |
|
2490 | |||
2491 | 385 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
2492 | $token = $this->lexer->glimpse(); |
||
2493 | } |
||
2494 | |||
2495 | 385 | if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) { |
|
2496 | // Peek beyond the matching closing parenthesis. |
||
2497 | 361 | $beyond = $this->lexer->peek(); |
|
2498 | |||
2499 | 361 | switch ($peek['value']) { |
|
2500 | case '(': |
||
2501 | // Peeks beyond the matched closing parenthesis. |
||
2502 | 37 | $token = $this->peekBeyondClosingParenthesis(false); |
|
2503 | |||
2504 | 37 | if ($token['type'] === Lexer::T_NOT) { |
|
2505 | 3 | $token = $this->lexer->peek(); |
|
2506 | } |
||
2507 | |||
2508 | 37 | if ($token['type'] === Lexer::T_IS) { |
|
2509 | 2 | $lookahead = $this->lexer->peek(); |
|
2510 | } |
||
2511 | 37 | break; |
|
2512 | |||
2513 | default: |
||
2514 | // Peek beyond the PathExpression or InputParameter. |
||
2515 | 333 | $token = $beyond; |
|
2516 | |||
2517 | 333 | while ($token['value'] === '.') { |
|
2518 | 288 | $this->lexer->peek(); |
|
2519 | |||
2520 | 288 | $token = $this->lexer->peek(); |
|
2521 | } |
||
2522 | |||
2523 | // Also peek beyond a NOT if there is one. |
||
2524 | 333 | if ($token['type'] === Lexer::T_NOT) { |
|
2525 | 11 | $token = $this->lexer->peek(); |
|
2526 | } |
||
2527 | |||
2528 | // We need to go even further in case of IS (differentiate between NULL and EMPTY) |
||
2529 | 333 | $lookahead = $this->lexer->peek(); |
|
2530 | } |
||
2531 | |||
2532 | // Also peek beyond a NOT if there is one. |
||
2533 | 361 | if ($lookahead['type'] === Lexer::T_NOT) { |
|
2534 | 7 | $lookahead = $this->lexer->peek(); |
|
2535 | } |
||
2536 | |||
2537 | 361 | $this->lexer->resetPeek(); |
|
2538 | } |
||
2539 | |||
2540 | 385 | if ($token['type'] === Lexer::T_BETWEEN) { |
|
2541 | 8 | return $this->BetweenExpression(); |
|
2542 | } |
||
2543 | |||
2544 | 379 | if ($token['type'] === Lexer::T_LIKE) { |
|
2545 | 14 | return $this->LikeExpression(); |
|
2546 | } |
||
2547 | |||
2548 | 366 | if ($token['type'] === Lexer::T_IN) { |
|
2549 | 35 | return $this->InExpression(); |
|
2550 | } |
||
2551 | |||
2552 | 340 | if ($token['type'] === Lexer::T_INSTANCE) { |
|
2553 | 17 | return $this->InstanceOfExpression(); |
|
2554 | } |
||
2555 | |||
2556 | 323 | if ($token['type'] === Lexer::T_MEMBER) { |
|
2557 | 8 | return $this->CollectionMemberExpression(); |
|
2558 | } |
||
2559 | |||
2560 | 315 | if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) { |
|
2561 | 13 | return $this->NullComparisonExpression(); |
|
2562 | } |
||
2563 | |||
2564 | 305 | if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_EMPTY) { |
|
2565 | 4 | return $this->EmptyCollectionComparisonExpression(); |
|
2566 | } |
||
2567 | |||
2568 | 301 | return $this->ComparisonExpression(); |
|
2569 | } |
||
2570 | |||
2571 | /** |
||
2572 | * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" |
||
2573 | * |
||
2574 | * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression |
||
2575 | */ |
||
2576 | 4 | public function EmptyCollectionComparisonExpression() |
|
2577 | { |
||
2578 | 4 | $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression( |
|
2579 | 4 | $this->CollectionValuedPathExpression() |
|
2580 | ); |
||
2581 | 4 | $this->match(Lexer::T_IS); |
|
2582 | |||
2583 | 4 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
2584 | 2 | $this->match(Lexer::T_NOT); |
|
2585 | 2 | $emptyCollectionCompExpr->not = true; |
|
2586 | } |
||
2587 | |||
2588 | 4 | $this->match(Lexer::T_EMPTY); |
|
2589 | |||
2590 | 4 | return $emptyCollectionCompExpr; |
|
2591 | } |
||
2592 | |||
2593 | /** |
||
2594 | * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression |
||
2595 | * |
||
2596 | * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression |
||
2597 | * SimpleEntityExpression ::= IdentificationVariable | InputParameter |
||
2598 | * |
||
2599 | * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression |
||
2600 | */ |
||
2601 | 8 | public function CollectionMemberExpression() |
|
2602 | { |
||
2603 | 8 | $not = false; |
|
2604 | 8 | $entityExpr = $this->EntityExpression(); |
|
2605 | |||
2606 | 8 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
2607 | $this->match(Lexer::T_NOT); |
||
2608 | |||
2609 | $not = true; |
||
2610 | } |
||
2611 | |||
2612 | 8 | $this->match(Lexer::T_MEMBER); |
|
2613 | |||
2614 | 8 | if ($this->lexer->isNextToken(Lexer::T_OF)) { |
|
2615 | 8 | $this->match(Lexer::T_OF); |
|
2616 | } |
||
2617 | |||
2618 | 8 | $collMemberExpr = new AST\CollectionMemberExpression( |
|
2619 | 8 | $entityExpr, |
|
2620 | 8 | $this->CollectionValuedPathExpression() |
|
2621 | ); |
||
2622 | 8 | $collMemberExpr->not = $not; |
|
2623 | |||
2624 | 8 | return $collMemberExpr; |
|
2625 | } |
||
2626 | |||
2627 | /** |
||
2628 | * Literal ::= string | char | integer | float | boolean |
||
2629 | * |
||
2630 | * @return \Doctrine\ORM\Query\AST\Literal |
||
2631 | */ |
||
2632 | 186 | public function Literal() |
|
2633 | { |
||
2634 | 186 | switch ($this->lexer->lookahead['type']) { |
|
2635 | case Lexer::T_STRING: |
||
2636 | 48 | $this->match(Lexer::T_STRING); |
|
2637 | |||
2638 | 48 | return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); |
|
2639 | case Lexer::T_INTEGER: |
||
2640 | case Lexer::T_FLOAT: |
||
2641 | 139 | $this->match( |
|
2642 | 139 | $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT |
|
2643 | ); |
||
2644 | |||
2645 | 139 | return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']); |
|
2646 | case Lexer::T_TRUE: |
||
2647 | case Lexer::T_FALSE: |
||
2648 | 8 | $this->match( |
|
2649 | 8 | $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE |
|
2650 | ); |
||
2651 | |||
2652 | 8 | return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']); |
|
2653 | default: |
||
2654 | $this->syntaxError('Literal'); |
||
2655 | } |
||
2656 | } |
||
2657 | |||
2658 | /** |
||
2659 | * InParameter ::= Literal | InputParameter |
||
2660 | * |
||
2661 | * @return string | \Doctrine\ORM\Query\AST\InputParameter |
||
2662 | */ |
||
2663 | 26 | public function InParameter() |
|
2664 | { |
||
2665 | 26 | if ($this->lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) { |
|
2666 | 14 | return $this->InputParameter(); |
|
2667 | } |
||
2668 | |||
2669 | 12 | return $this->Literal(); |
|
2670 | } |
||
2671 | |||
2672 | /** |
||
2673 | * InputParameter ::= PositionalParameter | NamedParameter |
||
2674 | * |
||
2675 | * @return \Doctrine\ORM\Query\AST\InputParameter |
||
2676 | */ |
||
2677 | 168 | public function InputParameter() |
|
2678 | { |
||
2679 | 168 | $this->match(Lexer::T_INPUT_PARAMETER); |
|
2680 | |||
2681 | 168 | return new AST\InputParameter($this->lexer->token['value']); |
|
2682 | } |
||
2683 | |||
2684 | /** |
||
2685 | * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" |
||
2686 | * |
||
2687 | * @return \Doctrine\ORM\Query\AST\ArithmeticExpression |
||
2688 | */ |
||
2689 | 335 | public function ArithmeticExpression() |
|
2690 | { |
||
2691 | 335 | $expr = new AST\ArithmeticExpression; |
|
2692 | |||
2693 | 335 | if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { |
|
2694 | 19 | $peek = $this->lexer->glimpse(); |
|
2695 | |||
2696 | 19 | if ($peek['type'] === Lexer::T_SELECT) { |
|
2697 | 7 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2698 | 7 | $expr->subselect = $this->Subselect(); |
|
2699 | 7 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2700 | |||
2701 | 7 | return $expr; |
|
2702 | } |
||
2703 | } |
||
2704 | |||
2705 | 335 | $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression(); |
|
2706 | |||
2707 | 335 | return $expr; |
|
2708 | } |
||
2709 | |||
2710 | /** |
||
2711 | * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* |
||
2712 | * |
||
2713 | * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression |
||
2714 | */ |
||
2715 | 440 | public function SimpleArithmeticExpression() |
|
2716 | { |
||
2717 | 440 | $terms = []; |
|
2718 | 440 | $terms[] = $this->ArithmeticTerm(); |
|
2719 | |||
2720 | 440 | while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) { |
|
2721 | 21 | $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); |
|
2722 | |||
2723 | 21 | $terms[] = $this->lexer->token['value']; |
|
2724 | 21 | $terms[] = $this->ArithmeticTerm(); |
|
2725 | } |
||
2726 | |||
2727 | // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression |
||
2728 | // if only one AST\ArithmeticTerm is defined |
||
2729 | 440 | if (\count($terms) === 1) { |
|
2730 | 435 | return $terms[0]; |
|
2731 | } |
||
2732 | |||
2733 | 21 | return new AST\SimpleArithmeticExpression($terms); |
|
2734 | } |
||
2735 | |||
2736 | /** |
||
2737 | * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* |
||
2738 | * |
||
2739 | * @return \Doctrine\ORM\Query\AST\ArithmeticTerm |
||
2740 | */ |
||
2741 | 440 | public function ArithmeticTerm() |
|
2742 | { |
||
2743 | 440 | $factors = []; |
|
2744 | 440 | $factors[] = $this->ArithmeticFactor(); |
|
2745 | |||
2746 | 440 | while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) { |
|
2747 | 53 | $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE); |
|
2748 | |||
2749 | 53 | $factors[] = $this->lexer->token['value']; |
|
2750 | 53 | $factors[] = $this->ArithmeticFactor(); |
|
2751 | } |
||
2752 | |||
2753 | // Phase 1 AST optimization: Prevent AST\ArithmeticTerm |
||
2754 | // if only one AST\ArithmeticFactor is defined |
||
2755 | 440 | if (\count($factors) === 1) { |
|
2756 | 412 | return $factors[0]; |
|
2757 | } |
||
2758 | |||
2759 | 53 | return new AST\ArithmeticTerm($factors); |
|
2760 | } |
||
2761 | |||
2762 | /** |
||
2763 | * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary |
||
2764 | * |
||
2765 | * @return \Doctrine\ORM\Query\AST\ArithmeticFactor |
||
2766 | */ |
||
2767 | 440 | public function ArithmeticFactor() |
|
2768 | { |
||
2769 | 440 | $sign = null; |
|
2770 | 440 | $isPlus = $this->lexer->isNextToken(Lexer::T_PLUS); |
|
2771 | |||
2772 | 440 | if ($isPlus || $this->lexer->isNextToken(Lexer::T_MINUS)) { |
|
2773 | 3 | $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS); |
|
2774 | 3 | $sign = $isPlus; |
|
2775 | } |
||
2776 | |||
2777 | 440 | $primary = $this->ArithmeticPrimary(); |
|
2778 | |||
2779 | // Phase 1 AST optimization: Prevent AST\ArithmeticFactor |
||
2780 | // if only one AST\ArithmeticPrimary is defined |
||
2781 | 440 | if ($sign === null) { |
|
2782 | 439 | return $primary; |
|
2783 | } |
||
2784 | |||
2785 | 3 | return new AST\ArithmeticFactor($primary, $sign); |
|
2786 | } |
||
2787 | |||
2788 | /** |
||
2789 | * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression |
||
2790 | * | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings |
||
2791 | * | FunctionsReturningDatetime | IdentificationVariable | ResultVariable |
||
2792 | * | InputParameter | CaseExpression |
||
2793 | */ |
||
2794 | 453 | public function ArithmeticPrimary() |
|
2795 | { |
||
2796 | 453 | if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { |
|
2797 | 25 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2798 | |||
2799 | 25 | $expr = $this->SimpleArithmeticExpression(); |
|
2800 | |||
2801 | 25 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2802 | |||
2803 | 25 | return new AST\ParenthesisExpression($expr); |
|
2804 | } |
||
2805 | |||
2806 | 453 | switch ($this->lexer->lookahead['type']) { |
|
2807 | case Lexer::T_COALESCE: |
||
2808 | case Lexer::T_NULLIF: |
||
2809 | case Lexer::T_CASE: |
||
2810 | 4 | return $this->CaseExpression(); |
|
2811 | |||
2812 | case Lexer::T_IDENTIFIER: |
||
2813 | 423 | $peek = $this->lexer->glimpse(); |
|
2814 | |||
2815 | 423 | if ($peek['value'] === '(') { |
|
2816 | 41 | return $this->FunctionDeclaration(); |
|
2817 | } |
||
2818 | |||
2819 | 394 | if ($peek['value'] === '.') { |
|
2820 | 383 | return $this->SingleValuedPathExpression(); |
|
2821 | } |
||
2822 | |||
2823 | 46 | if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) { |
|
2824 | 10 | return $this->ResultVariable(); |
|
2825 | } |
||
2826 | |||
2827 | 38 | return $this->StateFieldPathExpression(); |
|
2828 | |||
2829 | case Lexer::T_INPUT_PARAMETER: |
||
2830 | 149 | return $this->InputParameter(); |
|
2831 | |||
2832 | default: |
||
2833 | 180 | $peek = $this->lexer->glimpse(); |
|
2834 | |||
2835 | 180 | if ($peek['value'] === '(') { |
|
2836 | 18 | return $this->FunctionDeclaration(); |
|
2837 | } |
||
2838 | |||
2839 | 176 | return $this->Literal(); |
|
2840 | } |
||
2841 | } |
||
2842 | |||
2843 | /** |
||
2844 | * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")" |
||
2845 | * |
||
2846 | * @return \Doctrine\ORM\Query\AST\Subselect | |
||
2847 | * string |
||
2848 | */ |
||
2849 | 14 | public function StringExpression() |
|
2850 | { |
||
2851 | 14 | $peek = $this->lexer->glimpse(); |
|
2852 | |||
2853 | // Subselect |
||
2854 | 14 | if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) { |
|
2855 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
||
2856 | $expr = $this->Subselect(); |
||
2857 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
||
2858 | |||
2859 | return $expr; |
||
2860 | } |
||
2861 | |||
2862 | // ResultVariable (string) |
||
2863 | 14 | if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && |
|
2864 | 14 | isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) { |
|
2865 | 2 | return $this->ResultVariable(); |
|
2866 | } |
||
2867 | |||
2868 | 12 | return $this->StringPrimary(); |
|
2869 | } |
||
2870 | |||
2871 | /** |
||
2872 | * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression |
||
2873 | */ |
||
2874 | 63 | public function StringPrimary() |
|
2875 | { |
||
2876 | 63 | $lookaheadType = $this->lexer->lookahead['type']; |
|
2877 | |||
2878 | 63 | switch ($lookaheadType) { |
|
2879 | case Lexer::T_IDENTIFIER: |
||
2880 | 35 | $peek = $this->lexer->glimpse(); |
|
2881 | |||
2882 | 35 | if ($peek['value'] === '.') { |
|
2883 | 35 | return $this->StateFieldPathExpression(); |
|
2884 | } |
||
2885 | |||
2886 | 8 | if ($peek['value'] === '(') { |
|
2887 | // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions. |
||
2888 | 8 | return $this->FunctionDeclaration(); |
|
2889 | } |
||
2890 | |||
2891 | $this->syntaxError("'.' or '('"); |
||
2892 | break; |
||
2893 | |||
2894 | case Lexer::T_STRING: |
||
2895 | 45 | $this->match(Lexer::T_STRING); |
|
2896 | |||
2897 | 45 | return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); |
|
2898 | |||
2899 | case Lexer::T_INPUT_PARAMETER: |
||
2900 | 2 | return $this->InputParameter(); |
|
2901 | |||
2902 | case Lexer::T_CASE: |
||
2903 | case Lexer::T_COALESCE: |
||
2904 | case Lexer::T_NULLIF: |
||
2905 | return $this->CaseExpression(); |
||
2906 | } |
||
2907 | |||
2908 | $this->syntaxError( |
||
2909 | 'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression' |
||
2910 | ); |
||
2911 | } |
||
2912 | |||
2913 | /** |
||
2914 | * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression |
||
2915 | * |
||
2916 | * @return \Doctrine\ORM\Query\AST\PathExpression | |
||
2917 | * \Doctrine\ORM\Query\AST\SimpleEntityExpression |
||
2918 | */ |
||
2919 | 8 | public function EntityExpression() |
|
2920 | { |
||
2921 | 8 | $glimpse = $this->lexer->glimpse(); |
|
2922 | |||
2923 | 8 | if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') { |
|
2924 | 1 | return $this->SingleValuedAssociationPathExpression(); |
|
2925 | } |
||
2926 | |||
2927 | 7 | return $this->SimpleEntityExpression(); |
|
2928 | } |
||
2929 | |||
2930 | /** |
||
2931 | * SimpleEntityExpression ::= IdentificationVariable | InputParameter |
||
2932 | * |
||
2933 | * @return string | \Doctrine\ORM\Query\AST\InputParameter |
||
2934 | */ |
||
2935 | 7 | public function SimpleEntityExpression() |
|
2936 | { |
||
2937 | 7 | if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { |
|
2938 | 5 | return $this->InputParameter(); |
|
2939 | } |
||
2940 | |||
2941 | 2 | return $this->StateFieldPathExpression(); |
|
2942 | } |
||
2943 | |||
2944 | /** |
||
2945 | * AggregateExpression ::= |
||
2946 | * ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")" |
||
2947 | * |
||
2948 | * @return \Doctrine\ORM\Query\AST\AggregateExpression |
||
2949 | */ |
||
2950 | 88 | public function AggregateExpression() |
|
2951 | { |
||
2952 | 88 | $lookaheadType = $this->lexer->lookahead['type']; |
|
2953 | 88 | $isDistinct = false; |
|
2954 | |||
2955 | 88 | if (! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM])) { |
|
2956 | $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT'); |
||
2957 | } |
||
2958 | |||
2959 | 88 | $this->match($lookaheadType); |
|
2960 | 88 | $functionName = $this->lexer->token['value']; |
|
2961 | 88 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2962 | |||
2963 | 88 | if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) { |
|
2964 | 3 | $this->match(Lexer::T_DISTINCT); |
|
2965 | 3 | $isDistinct = true; |
|
2966 | } |
||
2967 | |||
2968 | 88 | $pathExp = $this->SimpleArithmeticExpression(); |
|
2969 | |||
2970 | 88 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2971 | |||
2972 | 88 | return new AST\AggregateExpression($functionName, $pathExp, $isDistinct); |
|
2973 | } |
||
2974 | |||
2975 | /** |
||
2976 | * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" |
||
2977 | * |
||
2978 | * @return \Doctrine\ORM\Query\AST\QuantifiedExpression |
||
2979 | */ |
||
2980 | 3 | public function QuantifiedExpression() |
|
2981 | { |
||
2982 | 3 | $lookaheadType = $this->lexer->lookahead['type']; |
|
2983 | 3 | $value = $this->lexer->lookahead['value']; |
|
2984 | |||
2985 | 3 | if (! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME])) { |
|
2986 | $this->syntaxError('ALL, ANY or SOME'); |
||
2987 | } |
||
2988 | |||
2989 | 3 | $this->match($lookaheadType); |
|
2990 | 3 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
2991 | |||
2992 | 3 | $qExpr = new AST\QuantifiedExpression($this->Subselect()); |
|
2993 | 3 | $qExpr->type = $value; |
|
2994 | |||
2995 | 3 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
2996 | |||
2997 | 3 | return $qExpr; |
|
2998 | } |
||
2999 | |||
3000 | /** |
||
3001 | * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression |
||
3002 | * |
||
3003 | * @return \Doctrine\ORM\Query\AST\BetweenExpression |
||
3004 | */ |
||
3005 | 8 | public function BetweenExpression() |
|
3006 | { |
||
3007 | 8 | $not = false; |
|
3008 | 8 | $arithExpr1 = $this->ArithmeticExpression(); |
|
3009 | |||
3010 | 8 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
3011 | 3 | $this->match(Lexer::T_NOT); |
|
3012 | 3 | $not = true; |
|
3013 | } |
||
3014 | |||
3015 | 8 | $this->match(Lexer::T_BETWEEN); |
|
3016 | 8 | $arithExpr2 = $this->ArithmeticExpression(); |
|
3017 | 8 | $this->match(Lexer::T_AND); |
|
3018 | 8 | $arithExpr3 = $this->ArithmeticExpression(); |
|
3019 | |||
3020 | 8 | $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3); |
|
3021 | 8 | $betweenExpr->not = $not; |
|
3022 | |||
3023 | 8 | return $betweenExpr; |
|
3024 | } |
||
3025 | |||
3026 | /** |
||
3027 | * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) |
||
3028 | * |
||
3029 | * @return \Doctrine\ORM\Query\AST\ComparisonExpression |
||
3030 | */ |
||
3031 | 301 | public function ComparisonExpression() |
|
3032 | { |
||
3033 | 301 | $this->lexer->glimpse(); |
|
3034 | |||
3035 | 301 | $leftExpr = $this->ArithmeticExpression(); |
|
3036 | 301 | $operator = $this->ComparisonOperator(); |
|
3037 | 301 | $rightExpr = ($this->isNextAllAnySome()) |
|
3038 | 3 | ? $this->QuantifiedExpression() |
|
3039 | 301 | : $this->ArithmeticExpression(); |
|
3040 | |||
3041 | 299 | return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr); |
|
3042 | } |
||
3043 | |||
3044 | /** |
||
3045 | * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" |
||
3046 | * |
||
3047 | * @return \Doctrine\ORM\Query\AST\InExpression |
||
3048 | */ |
||
3049 | 35 | public function InExpression() |
|
3050 | { |
||
3051 | 35 | $inExpression = new AST\InExpression($this->ArithmeticExpression()); |
|
3052 | |||
3053 | 35 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
3054 | 6 | $this->match(Lexer::T_NOT); |
|
3055 | 6 | $inExpression->not = true; |
|
3056 | } |
||
3057 | |||
3058 | 35 | $this->match(Lexer::T_IN); |
|
3059 | 35 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
3060 | |||
3061 | 35 | if ($this->lexer->isNextToken(Lexer::T_SELECT)) { |
|
3062 | 9 | $inExpression->subselect = $this->Subselect(); |
|
3063 | } else { |
||
3064 | 26 | $literals = []; |
|
3065 | 26 | $literals[] = $this->InParameter(); |
|
3066 | |||
3067 | 26 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
3068 | 16 | $this->match(Lexer::T_COMMA); |
|
3069 | 16 | $literals[] = $this->InParameter(); |
|
3070 | } |
||
3071 | |||
3072 | 26 | $inExpression->literals = $literals; |
|
3073 | } |
||
3074 | |||
3075 | 34 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
3076 | |||
3077 | 34 | return $inExpression; |
|
3078 | } |
||
3079 | |||
3080 | /** |
||
3081 | * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") |
||
3082 | * |
||
3083 | * @return \Doctrine\ORM\Query\AST\InstanceOfExpression |
||
3084 | */ |
||
3085 | 17 | public function InstanceOfExpression() |
|
3086 | { |
||
3087 | 17 | $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable()); |
|
3088 | |||
3089 | 17 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
3090 | 1 | $this->match(Lexer::T_NOT); |
|
3091 | 1 | $instanceOfExpression->not = true; |
|
3092 | } |
||
3093 | |||
3094 | 17 | $this->match(Lexer::T_INSTANCE); |
|
3095 | 17 | $this->match(Lexer::T_OF); |
|
3096 | |||
3097 | 17 | $exprValues = []; |
|
3098 | |||
3099 | 17 | if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) { |
|
3100 | 2 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
3101 | |||
3102 | 2 | $exprValues[] = $this->InstanceOfParameter(); |
|
3103 | |||
3104 | 2 | while ($this->lexer->isNextToken(Lexer::T_COMMA)) { |
|
3105 | 2 | $this->match(Lexer::T_COMMA); |
|
3106 | |||
3107 | 2 | $exprValues[] = $this->InstanceOfParameter(); |
|
3108 | } |
||
3109 | |||
3110 | 2 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
3111 | |||
3112 | 2 | $instanceOfExpression->value = $exprValues; |
|
3113 | |||
3114 | 2 | return $instanceOfExpression; |
|
3115 | } |
||
3116 | |||
3117 | 15 | $exprValues[] = $this->InstanceOfParameter(); |
|
3118 | |||
3119 | 15 | $instanceOfExpression->value = $exprValues; |
|
3120 | |||
3121 | 15 | return $instanceOfExpression; |
|
3122 | } |
||
3123 | |||
3124 | /** |
||
3125 | * InstanceOfParameter ::= AbstractSchemaName | InputParameter |
||
3126 | * |
||
3127 | * @return mixed |
||
3128 | */ |
||
3129 | 17 | public function InstanceOfParameter() |
|
3130 | { |
||
3131 | 17 | if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { |
|
3132 | 6 | $this->match(Lexer::T_INPUT_PARAMETER); |
|
3133 | |||
3134 | 6 | return new AST\InputParameter($this->lexer->token['value']); |
|
3135 | } |
||
3136 | |||
3137 | 11 | $abstractSchemaName = $this->AbstractSchemaName(); |
|
3138 | |||
3139 | 11 | $this->validateAbstractSchemaName($abstractSchemaName); |
|
3140 | |||
3141 | 11 | return $abstractSchemaName; |
|
3142 | } |
||
3143 | |||
3144 | /** |
||
3145 | * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char] |
||
3146 | * |
||
3147 | * @return \Doctrine\ORM\Query\AST\LikeExpression |
||
3148 | */ |
||
3149 | 14 | public function LikeExpression() |
|
3150 | { |
||
3151 | 14 | $stringExpr = $this->StringExpression(); |
|
3152 | 14 | $not = false; |
|
3153 | |||
3154 | 14 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
3155 | 3 | $this->match(Lexer::T_NOT); |
|
3156 | 3 | $not = true; |
|
3157 | } |
||
3158 | |||
3159 | 14 | $this->match(Lexer::T_LIKE); |
|
3160 | |||
3161 | 14 | if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) { |
|
3162 | 3 | $this->match(Lexer::T_INPUT_PARAMETER); |
|
3163 | 3 | $stringPattern = new AST\InputParameter($this->lexer->token['value']); |
|
3164 | } else { |
||
3165 | 12 | $stringPattern = $this->StringPrimary(); |
|
3166 | } |
||
3167 | |||
3168 | 14 | $escapeChar = null; |
|
3169 | |||
3170 | 14 | if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) { |
|
3171 | 2 | $this->match(Lexer::T_ESCAPE); |
|
3172 | 2 | $this->match(Lexer::T_STRING); |
|
3173 | |||
3174 | 2 | $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']); |
|
3175 | } |
||
3176 | |||
3177 | 14 | $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar); |
|
3178 | 14 | $likeExpr->not = $not; |
|
3179 | |||
3180 | 14 | return $likeExpr; |
|
3181 | } |
||
3182 | |||
3183 | /** |
||
3184 | * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL" |
||
3185 | * |
||
3186 | * @return \Doctrine\ORM\Query\AST\NullComparisonExpression |
||
3187 | */ |
||
3188 | 13 | public function NullComparisonExpression() |
|
3189 | { |
||
3190 | switch (true) { |
||
3191 | 13 | case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER): |
|
3192 | $this->match(Lexer::T_INPUT_PARAMETER); |
||
3193 | |||
3194 | $expr = new AST\InputParameter($this->lexer->token['value']); |
||
3195 | break; |
||
3196 | |||
3197 | 13 | case $this->lexer->isNextToken(Lexer::T_NULLIF): |
|
3198 | 1 | $expr = $this->NullIfExpression(); |
|
3199 | 1 | break; |
|
3200 | |||
3201 | 13 | case $this->lexer->isNextToken(Lexer::T_COALESCE): |
|
3202 | 1 | $expr = $this->CoalesceExpression(); |
|
3203 | 1 | break; |
|
3204 | |||
3205 | 13 | case $this->isFunction(): |
|
3206 | 2 | $expr = $this->FunctionDeclaration(); |
|
3207 | 2 | break; |
|
3208 | |||
3209 | default: |
||
3210 | // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression |
||
3211 | 12 | $glimpse = $this->lexer->glimpse(); |
|
3212 | |||
3213 | 12 | if ($glimpse['type'] === Lexer::T_DOT) { |
|
3214 | 8 | $expr = $this->SingleValuedPathExpression(); |
|
3215 | |||
3216 | // Leave switch statement |
||
3217 | 8 | break; |
|
3218 | } |
||
3219 | |||
3220 | 4 | $lookaheadValue = $this->lexer->lookahead['value']; |
|
3221 | |||
3222 | // Validate existing component |
||
3223 | 4 | if (! isset($this->queryComponents[$lookaheadValue])) { |
|
3224 | $this->semanticalError('Cannot add having condition on undefined result variable.'); |
||
3225 | } |
||
3226 | |||
3227 | // Validate SingleValuedPathExpression (ie.: "product") |
||
3228 | 4 | if (isset($this->queryComponents[$lookaheadValue]['metadata'])) { |
|
3229 | 1 | $expr = $this->SingleValuedPathExpression(); |
|
3230 | 1 | break; |
|
3231 | } |
||
3232 | |||
3233 | // Validating ResultVariable |
||
3234 | 3 | if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) { |
|
3235 | $this->semanticalError('Cannot add having condition on a non result variable.'); |
||
3236 | } |
||
3237 | |||
3238 | 3 | $expr = $this->ResultVariable(); |
|
3239 | 3 | break; |
|
3240 | } |
||
3241 | |||
3242 | 13 | $nullCompExpr = new AST\NullComparisonExpression($expr); |
|
3243 | |||
3244 | 13 | $this->match(Lexer::T_IS); |
|
3245 | |||
3246 | 13 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
3247 | 5 | $this->match(Lexer::T_NOT); |
|
3248 | |||
3249 | 5 | $nullCompExpr->not = true; |
|
3250 | } |
||
3251 | |||
3252 | 13 | $this->match(Lexer::T_NULL); |
|
3253 | |||
3254 | 13 | return $nullCompExpr; |
|
3255 | } |
||
3256 | |||
3257 | /** |
||
3258 | * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" |
||
3259 | * |
||
3260 | * @return \Doctrine\ORM\Query\AST\ExistsExpression |
||
3261 | */ |
||
3262 | 7 | public function ExistsExpression() |
|
3263 | { |
||
3264 | 7 | $not = false; |
|
3265 | |||
3266 | 7 | if ($this->lexer->isNextToken(Lexer::T_NOT)) { |
|
3267 | $this->match(Lexer::T_NOT); |
||
3268 | $not = true; |
||
3269 | } |
||
3270 | |||
3271 | 7 | $this->match(Lexer::T_EXISTS); |
|
3272 | 7 | $this->match(Lexer::T_OPEN_PARENTHESIS); |
|
3273 | |||
3274 | 7 | $existsExpression = new AST\ExistsExpression($this->Subselect()); |
|
3275 | 7 | $existsExpression->not = $not; |
|
3276 | |||
3277 | 7 | $this->match(Lexer::T_CLOSE_PARENTHESIS); |
|
3278 | |||
3279 | 7 | return $existsExpression; |
|
3280 | } |
||
3281 | |||
3282 | /** |
||
3283 | * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" |
||
3284 | * |
||
3285 | * @return string |
||
3286 | */ |
||
3287 | 301 | public function ComparisonOperator() |
|
3288 | { |
||
3289 | 301 | switch ($this->lexer->lookahead['value']) { |
|
3290 | case '=': |
||
3291 | 250 | $this->match(Lexer::T_EQUALS); |
|
3292 | |||
3293 | 250 | return '='; |
|
3294 | |||
3295 | case '<': |
||
3296 | 17 | $this->match(Lexer::T_LOWER_THAN); |
|
3297 | 17 | $operator = '<'; |
|
3298 | |||
3299 | 17 | if ($this->lexer->isNextToken(Lexer::T_EQUALS)) { |
|
3300 | 5 | $this->match(Lexer::T_EQUALS); |
|
3301 | 5 | $operator .= '='; |
|
3302 | 12 | } elseif ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) { |
|
3303 | 3 | $this->match(Lexer::T_GREATER_THAN); |
|
3304 | 3 | $operator .= '>'; |
|
3305 | } |
||
3306 | |||
3307 | 17 | return $operator; |
|
3308 | |||
3309 | case '>': |
||
3310 | 47 | $this->match(Lexer::T_GREATER_THAN); |
|
3311 | 47 | $operator = '>'; |
|
3312 | |||
3313 | 47 | if ($this->lexer->isNextToken(Lexer::T_EQUALS)) { |
|
3314 | 6 | $this->match(Lexer::T_EQUALS); |
|
3315 | 6 | $operator .= '='; |
|
3316 | } |
||
3317 | |||
3318 | 47 | return $operator; |
|
3319 | |||
3320 | case '!': |
||
3321 | 6 | $this->match(Lexer::T_NEGATE); |
|
3322 | 6 | $this->match(Lexer::T_EQUALS); |
|
3323 | |||
3324 | 6 | return '<>'; |
|
3325 | |||
3326 | default: |
||
3327 | $this->syntaxError('=, <, <=, <>, >, >=, !='); |
||
3328 | } |
||
3329 | } |
||
3330 | |||
3331 | /** |
||
3332 | * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime |
||
3333 | * |
||
3334 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3335 | */ |
||
3336 | 156 | public function FunctionDeclaration() |
|
3337 | { |
||
3338 | 156 | $token = $this->lexer->lookahead; |
|
3339 | 156 | $funcName = strtolower($token['value']); |
|
3340 | |||
3341 | 156 | $customFunctionDeclaration = $this->CustomFunctionDeclaration(); |
|
3342 | |||
3343 | // Check for custom functions functions first! |
||
3344 | 156 | switch (true) { |
|
3345 | case $customFunctionDeclaration !== null: |
||
3346 | 4 | return $customFunctionDeclaration; |
|
3347 | |||
3348 | 152 | case (isset(self::$_STRING_FUNCTIONS[$funcName])): |
|
3349 | 33 | return $this->FunctionsReturningStrings(); |
|
3350 | |||
3351 | 124 | case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])): |
|
3352 | 109 | return $this->FunctionsReturningNumerics(); |
|
3353 | |||
3354 | 16 | case (isset(self::$_DATETIME_FUNCTIONS[$funcName])): |
|
3355 | 16 | return $this->FunctionsReturningDatetime(); |
|
3356 | |||
3357 | default: |
||
3358 | $this->syntaxError('known function', $token); |
||
3359 | } |
||
3360 | } |
||
3361 | |||
3362 | /** |
||
3363 | * Helper function for FunctionDeclaration grammar rule. |
||
3364 | * |
||
3365 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3366 | */ |
||
3367 | 156 | private function CustomFunctionDeclaration() |
|
3368 | { |
||
3369 | 156 | $token = $this->lexer->lookahead; |
|
3370 | 156 | $funcName = strtolower($token['value']); |
|
3371 | |||
3372 | // Check for custom functions afterwards |
||
3373 | 156 | $config = $this->em->getConfiguration(); |
|
3374 | |||
3375 | 156 | switch (true) { |
|
3376 | 156 | case ($config->getCustomStringFunction($funcName) !== null): |
|
3377 | 3 | return $this->CustomFunctionsReturningStrings(); |
|
3378 | |||
3379 | 154 | case ($config->getCustomNumericFunction($funcName) !== null): |
|
3380 | 2 | return $this->CustomFunctionsReturningNumerics(); |
|
3381 | |||
3382 | 152 | case ($config->getCustomDatetimeFunction($funcName) !== null): |
|
3383 | return $this->CustomFunctionsReturningDatetime(); |
||
3384 | |||
3385 | default: |
||
3386 | 152 | return null; |
|
3387 | } |
||
3388 | } |
||
3389 | |||
3390 | /** |
||
3391 | * FunctionsReturningNumerics ::= |
||
3392 | * "LENGTH" "(" StringPrimary ")" | |
||
3393 | * "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | |
||
3394 | * "ABS" "(" SimpleArithmeticExpression ")" | |
||
3395 | * "SQRT" "(" SimpleArithmeticExpression ")" | |
||
3396 | * "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | |
||
3397 | * "SIZE" "(" CollectionValuedPathExpression ")" | |
||
3398 | * "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | |
||
3399 | * "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | |
||
3400 | * "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
||
3401 | * |
||
3402 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3403 | */ |
||
3404 | 109 | public function FunctionsReturningNumerics() |
|
3405 | { |
||
3406 | 109 | $funcNameLower = strtolower($this->lexer->lookahead['value']); |
|
3407 | 109 | $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower]; |
|
3408 | |||
3409 | 109 | $function = new $funcClass($funcNameLower); |
|
3410 | 109 | $function->parse($this); |
|
3411 | |||
3412 | 109 | return $function; |
|
3413 | } |
||
3414 | |||
3415 | /** |
||
3416 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3417 | */ |
||
3418 | 2 | public function CustomFunctionsReturningNumerics() |
|
3419 | { |
||
3420 | // getCustomNumericFunction is case-insensitive |
||
3421 | 2 | $functionName = strtolower($this->lexer->lookahead['value']); |
|
3422 | 2 | $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName); |
|
3423 | |||
3424 | 2 | $function = is_string($functionClass) |
|
3425 | 1 | ? new $functionClass($functionName) |
|
3426 | 2 | : call_user_func($functionClass, $functionName); |
|
3427 | |||
3428 | 2 | $function->parse($this); |
|
3429 | |||
3430 | 2 | return $function; |
|
3431 | } |
||
3432 | |||
3433 | /** |
||
3434 | * FunctionsReturningDateTime ::= |
||
3435 | * "CURRENT_DATE" | |
||
3436 | * "CURRENT_TIME" | |
||
3437 | * "CURRENT_TIMESTAMP" | |
||
3438 | * "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" | |
||
3439 | * "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
||
3440 | * |
||
3441 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3442 | */ |
||
3443 | 16 | public function FunctionsReturningDatetime() |
|
3444 | { |
||
3445 | 16 | $funcNameLower = strtolower($this->lexer->lookahead['value']); |
|
3446 | 16 | $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower]; |
|
3447 | |||
3448 | 16 | $function = new $funcClass($funcNameLower); |
|
3449 | 16 | $function->parse($this); |
|
3450 | |||
3451 | 16 | return $function; |
|
3452 | } |
||
3453 | |||
3454 | /** |
||
3455 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3456 | */ |
||
3457 | public function CustomFunctionsReturningDatetime() |
||
3458 | { |
||
3459 | // getCustomDatetimeFunction is case-insensitive |
||
3460 | $functionName = $this->lexer->lookahead['value']; |
||
3461 | $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName); |
||
3462 | |||
3463 | $function = is_string($functionClass) |
||
3464 | ? new $functionClass($functionName) |
||
3465 | : call_user_func($functionClass, $functionName); |
||
3466 | |||
3467 | $function->parse($this); |
||
3468 | |||
3469 | return $function; |
||
3470 | } |
||
3471 | |||
3472 | /** |
||
3473 | * FunctionsReturningStrings ::= |
||
3474 | * "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" | |
||
3475 | * "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | |
||
3476 | * "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | |
||
3477 | * "LOWER" "(" StringPrimary ")" | |
||
3478 | * "UPPER" "(" StringPrimary ")" | |
||
3479 | * "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")" |
||
3480 | * |
||
3481 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3482 | */ |
||
3483 | 33 | public function FunctionsReturningStrings() |
|
3484 | { |
||
3485 | 33 | $funcNameLower = strtolower($this->lexer->lookahead['value']); |
|
3486 | 33 | $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower]; |
|
3487 | |||
3488 | 33 | $function = new $funcClass($funcNameLower); |
|
3489 | 33 | $function->parse($this); |
|
3490 | |||
3491 | 33 | return $function; |
|
3492 | } |
||
3493 | |||
3494 | /** |
||
3495 | * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode |
||
3496 | */ |
||
3497 | 3 | public function CustomFunctionsReturningStrings() |
|
3498 | { |
||
3499 | // getCustomStringFunction is case-insensitive |
||
3500 | 3 | $functionName = $this->lexer->lookahead['value']; |
|
3501 | 3 | $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName); |
|
3502 | |||
3503 | 3 | $function = is_string($functionClass) |
|
3504 | 2 | ? new $functionClass($functionName) |
|
3505 | 3 | : call_user_func($functionClass, $functionName); |
|
3506 | |||
3507 | 3 | $function->parse($this); |
|
3508 | |||
3509 | 3 | return $function; |
|
3510 | } |
||
3511 | } |
||
3512 |
The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.
The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.
To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.