Passed
Pull Request — master (#6709)
by Sergey
12:37
created

Parser::PartialObjectExpression()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 4.1417

Importance

Changes 0
Metric Value
cc 4
eloc 28
nc 6
nop 0
dl 0
loc 50
ccs 23
cts 29
cp 0.7931
crap 4.1417
rs 8.8571
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 168 and the first side effect is on line 19.

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.

Loading history...
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
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
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 839
    public function __construct(Query $query)
169
    {
170 839
        $this->query        = $query;
171 839
        $this->em           = $query->getEntityManager();
172 839
        $this->lexer        = new Lexer($query->getDQL());
173 839
        $this->parserResult = new ParserResult();
174 839
    }
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 120
    public function setCustomOutputTreeWalker($className)
183
    {
184 120
        $this->customOutputWalker = $className;
185 120
    }
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 10
    public function getEntityManager()
223
    {
224 10
        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 839
    public function getAST()
235
    {
236
        // Parse & build AST
237 839
        $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 804
        $this->processDeferredIdentificationVariables();
242
243 802
        if ($this->deferredPartialObjectExpressions) {
244 9
            $this->processDeferredPartialObjectExpressions();
245
        }
246
247 801
        if ($this->deferredPathExpressions) {
248 594
            $this->processDeferredPathExpressions();
249
        }
250
251 799
        if ($this->deferredResultVariables) {
252 32
            $this->processDeferredResultVariables();
253
        }
254
255 799
        if ($this->deferredNewObjectExpressions) {
256 27
            $this->processDeferredNewObjectExpressions($AST);
257
        }
258
259 795
        $this->processRootEntityAliasSelected();
260
261
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
262 794
        $this->fixIdentificationVariableOrder($AST);
263
264 794
        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 850
    public function match($token)
278
    {
279 850
        $lookaheadType = $this->lexer->lookahead['type'];
280
281
        // Short-circuit on first condition, usually types match
282 850
        if ($lookaheadType !== $token) {
283
            // If parameter is not identifier (1-99) must be exact match
284 21
            if ($token < Lexer::T_IDENTIFIER) {
285 2
                $this->syntaxError($this->lexer->getLiteral($token));
286
            }
287
288
            // If parameter is keyword (200+) must be exact match
289 19
            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 12
            if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
295 9
                $this->syntaxError($this->lexer->getLiteral($token));
296
            }
297
        }
298
299 843
        $this->lexer->moveNext();
300 843
    }
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 839
    public function parse()
328
    {
329 839
        $AST = $this->getAST();
330
331 794
        $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
332 794
        if ($customWalkers !== false) {
333 96
            $this->customTreeWalkers = $customWalkers;
334
        }
335
336 794
        $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER);
337
338 794
        if ($customOutputWalker !== false) {
339 79
            $this->customOutputWalker = $customOutputWalker;
340
        }
341
342
        // Run any custom tree walkers over the AST
343 794
        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 788
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
368 788
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
369
370
        // Assign an SQL executor to the parser result
371 788
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
372
373 780
        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 794
    private function fixIdentificationVariableOrder($AST)
386
    {
387 794
        if (count($this->identVariableExpressions) <= 1) {
388 619
            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, true);
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 26
    public function semanticalError($message = '', $token = null, ?\Throwable $previousFailure = null)
437
    {
438 26
        if ($token === null) {
439 2
            $token = $this->lexer->lookahead;
440
        }
441
442
        // Minimum exposed chars ahead of token
443 26
        $distance = 12;
444
445
        // Find a position of a final word to display in error string
446 26
        $dql    = $this->query->getDQL();
447 26
        $length = strlen($dql);
448 26
        $pos    = $token['position'] + $distance;
449 26
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
450 26
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
451
452 26
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
453 26
        $tokenStr = substr($dql, (int) $token['position'], $length);
454
455
        // Building informative message
456 26
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
457
458 26
        throw QueryException::semanticalError(
459 26
            $message,
460 26
            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:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
479 42
                    ++$numUnmatched;
480 42
                    break;
481
482
                case Lexer::T_CLOSE_PARENTHESIS:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
483 170
                    --$numUnmatched;
484 170
                    break;
485
486
                default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

As per the PSR-2 coding standard, default statements should not be wrapped in curly braces.

switch ($expr) {
    default: { //wrong
        doSomething();
        break;
    }
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
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 358
    private function isMathOperator($token)
508
    {
509 358
        return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY], true);
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 401
    private function isFunction()
518
    {
519 401
        $lookaheadType = $this->lexer->lookahead['type'];
520 401
        $peek          = $this->lexer->peek();
521
522 401
        $this->lexer->resetPeek();
523
524 401
        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], true);
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], true);
547
    }
548
549
    /**
550
     * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
551
     * It must exist in query components list.
552
     */
553 804
    private function processDeferredIdentificationVariables()
554
    {
555 804
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
556 783
            $identVariable = $deferredItem['expression'];
557
558
            // Check if IdentificationVariable exists in queryComponents
559 783
            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 783
            $qComp = $this->queryComponents[$identVariable];
567
568
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
569 783
            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 783
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
578 1
                $this->semanticalError(
579 1
                    sprintf("'%s' is used outside the scope of its declaration.", $identVariable),
580 783
                    $deferredItem['token']
581
                );
582
            }
583
        }
584 802
    }
585
586
    /**
587
     * Validates that the given <tt>NewObjectExpression</tt>.
588
     *
589
     * @param \Doctrine\ORM\Query\AST\SelectClause $AST
590
     */
591 27
    private function processDeferredNewObjectExpressions($AST)
592
    {
593 27
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
594 27
            $expression    = $deferredItem['expression'];
595 27
            $token         = $deferredItem['token'];
596 27
            $className     = $expression->className;
597 27
            $args          = $expression->args;
598 27
            $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
599
600
            // If the namespace is not given then assumes the first FROM entity namespace
601 27
            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 27
            if (! class_exists($className)) {
612 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
613
            }
614
615 26
            $class = new \ReflectionClass($className);
616
617 26
            if (! $class->isInstantiable()) {
618 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
619
            }
620
621 25
            if ($class->getConstructor() === null) {
622 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
623
            }
624
625 24
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
626 24
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
627
            }
628
        }
629 23
    }
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 594
    private function processDeferredPathExpressions()
711
    {
712 594
        foreach ($this->deferredPathExpressions as $deferredItem) {
713 594
            $pathExpression = $deferredItem['expression'];
714
715 594
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
716 594
            $class = $qComp['metadata'];
717 594
            $field = $pathExpression->field;
718
719 594
            if ($field === null) {
720 40
                $field = $pathExpression->field = $class->identifier[0];
721
            }
722
723 594
            $property = $class->getProperty($field);
724
725
            // Check if field or association exists
726 594
            if (! $property) {
727
                $this->semanticalError(
728
                    'Class ' . $class->getClassName() . ' has no field or association named ' . $field,
729
                    $deferredItem['token']
730
                );
731
            }
732
733 594
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
734
735 594
            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 594
            $expectedType = $pathExpression->expectedType;
744
745 594
            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 592
            $pathExpression->type = $fieldType;
775
        }
776 592
    }
777
778 795
    private function processRootEntityAliasSelected()
779
    {
780 795
        if (! $this->identVariableExpressions) {
781 234
            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 839
    public function QueryLanguage()
801
    {
802 839
        $statement = null;
803
804 839
        $this->lexer->moveNext();
805
806 839
        switch ($this->lexer->lookahead['type']) {
807
            case Lexer::T_SELECT:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
808 775
                $statement = $this->SelectStatement();
809 744
                break;
810
811
            case Lexer::T_UPDATE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
812 31
                $statement = $this->UpdateStatement();
813 31
                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 807
        if ($this->lexer->lookahead !== null) {
826 3
            $this->syntaxError('end of string');
827
        }
828
829 804
        return $statement;
830
    }
831
832
    /**
833
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
834
     *
835
     * @return \Doctrine\ORM\Query\AST\SelectStatement
836
     */
837 775
    public function SelectStatement()
838
    {
839 775
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
840
841 748
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
842 745
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
843 744
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
844 744
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
845
846 744
        return $selectStatement;
847
    }
848
849
    /**
850
     * UpdateStatement ::= UpdateClause [WhereClause]
851
     *
852
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
853
     */
854 31
    public function UpdateStatement()
855
    {
856 31
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
857
858 31
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
859
860 31
        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 807
    public function IdentificationVariable()
883
    {
884 807
        $this->match(Lexer::T_IDENTIFIER);
885
886 807
        $identVariable = $this->lexer->token['value'];
887
888 807
        $this->deferredIdentificationVariables[] = [
889 807
            'expression'   => $identVariable,
890 807
            'nestingLevel' => $this->nestingLevel,
891 807
            'token'        => $this->lexer->token,
892
        ];
893
894 807
        return $identVariable;
895
    }
896
897
    /**
898
     * AliasIdentificationVariable = identifier
899
     *
900
     * @return string
901
     */
902 815
    public function AliasIdentificationVariable()
903
    {
904 815
        $this->match(Lexer::T_IDENTIFIER);
905
906 815
        $aliasIdentVariable = $this->lexer->token['value'];
907 815
        $exists             = isset($this->queryComponents[$aliasIdentVariable]);
908
909 815
        if ($exists) {
910 2
            $this->semanticalError(sprintf("'%s' is already defined.", $aliasIdentVariable), $this->lexer->token);
911
        }
912
913 815
        return $aliasIdentVariable;
914
    }
915
916
    /**
917
     * AbstractSchemaName ::= fully_qualified_name | identifier
918
     *
919
     * @return string
920
     */
921 827
    public function AbstractSchemaName()
922
    {
923 827
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
924 820
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
925
926 820
            return $this->lexer->token['value'];
927
        }
928
929 19
        $this->match(Lexer::T_IDENTIFIER);
930
931 18
        return $this->lexer->token['value'];
932
    }
933
934
    /**
935
     * Validates an AbstractSchemaName, making sure the class exists.
936
     *
937
     * @param string $schemaName The name to validate.
938
     *
939
     * @throws QueryException If the name does not exist.
940
     */
941 823
    private function validateAbstractSchemaName($schemaName) : void
942
    {
943 823
        if (class_exists($schemaName, true) || interface_exists($schemaName, true)) {
944 814
            return;
945
        }
946
947
        try {
948 10
            $this->getEntityManager()->getClassMetadata($schemaName);
949
950 1
            return;
951 9
        } catch (MappingException $mappingException) {
952 9
            $this->semanticalError(
953 9
                \sprintf('Class %s could not be mapped', $schemaName),
954 9
                $this->lexer->token
955
            );
956
        }
957
958
        $this->semanticalError(sprintf("Class '%s' is not defined.", $schemaName), $this->lexer->token);
959
    }
960
961
    /**
962
     * AliasResultVariable ::= identifier
963
     *
964
     * @return string
965
     */
966 131
    public function AliasResultVariable()
967
    {
968 131
        $this->match(Lexer::T_IDENTIFIER);
969
970 127
        $resultVariable = $this->lexer->token['value'];
971 127
        $exists         = isset($this->queryComponents[$resultVariable]);
972
973 127
        if ($exists) {
974 2
            $this->semanticalError(sprintf("'%s' is already defined.", $resultVariable), $this->lexer->token);
975
        }
976
977 127
        return $resultVariable;
978
    }
979
980
    /**
981
     * ResultVariable ::= identifier
982
     *
983
     * @return string
984
     */
985 32
    public function ResultVariable()
986
    {
987 32
        $this->match(Lexer::T_IDENTIFIER);
988
989 32
        $resultVariable = $this->lexer->token['value'];
990
991
        // Defer ResultVariable validation
992 32
        $this->deferredResultVariables[] = [
993 32
            'expression'   => $resultVariable,
994 32
            'nestingLevel' => $this->nestingLevel,
995 32
            'token'        => $this->lexer->token,
996
        ];
997
998 32
        return $resultVariable;
999
    }
1000
1001
    /**
1002
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1003
     *
1004
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1005
     */
1006 257
    public function JoinAssociationPathExpression()
1007
    {
1008 257
        $identVariable = $this->IdentificationVariable();
1009
1010 257
        if (! isset($this->queryComponents[$identVariable])) {
1011
            $this->semanticalError(
1012
                'Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.'
1013
            );
1014
        }
1015
1016 257
        $this->match(Lexer::T_DOT);
1017 257
        $this->match(Lexer::T_IDENTIFIER);
1018
1019 257
        $field = $this->lexer->token['value'];
1020
1021
        // Validate association field
1022 257
        $qComp    = $this->queryComponents[$identVariable];
1023 257
        $class    = $qComp['metadata'];
1024 257
        $property = $class->getProperty($field);
1025
1026 257
        if (! ($property !== null && $property instanceof AssociationMetadata)) {
1027
            $this->semanticalError('Class ' . $class->getClassName() . ' has no association named ' . $field);
1028
        }
1029
1030 257
        return new AST\JoinAssociationPathExpression($identVariable, $field);
1031
    }
1032
1033
    /**
1034
     * Parses an arbitrary path expression and defers semantical validation
1035
     * based on expected types.
1036
     *
1037
     * PathExpression ::= IdentificationVariable {"." identifier}*
1038
     *
1039
     * @param int $expectedTypes
1040
     *
1041
     * @return \Doctrine\ORM\Query\AST\PathExpression
1042
     */
1043 604
    public function PathExpression($expectedTypes)
1044
    {
1045 604
        $identVariable = $this->IdentificationVariable();
1046 604
        $field         = null;
1047
1048 604
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1049 598
            $this->match(Lexer::T_DOT);
1050 598
            $this->match(Lexer::T_IDENTIFIER);
1051
1052 598
            $field = $this->lexer->token['value'];
1053
1054 598
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1055
                $this->match(Lexer::T_DOT);
1056
                $this->match(Lexer::T_IDENTIFIER);
1057
                $field .= '.' . $this->lexer->token['value'];
1058
            }
1059
        }
1060
1061
        // Creating AST node
1062 604
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1063
1064
        // Defer PathExpression validation if requested to be deferred
1065 604
        $this->deferredPathExpressions[] = [
1066 604
            'expression'   => $pathExpr,
1067 604
            'nestingLevel' => $this->nestingLevel,
1068 604
            'token'        => $this->lexer->token,
1069
        ];
1070
1071 604
        return $pathExpr;
1072
    }
1073
1074
    /**
1075
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
1076
     *
1077
     * @return \Doctrine\ORM\Query\AST\PathExpression
1078
     */
1079
    public function AssociationPathExpression()
1080
    {
1081
        return $this->PathExpression(
1082
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
1083
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
1084
        );
1085
    }
1086
1087
    /**
1088
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1089
     *
1090
     * @return \Doctrine\ORM\Query\AST\PathExpression
1091
     */
1092 513
    public function SingleValuedPathExpression()
1093
    {
1094 513
        return $this->PathExpression(
1095 513
            AST\PathExpression::TYPE_STATE_FIELD |
1096 513
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1097
        );
1098
    }
1099
1100
    /**
1101
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1102
     *
1103
     * @return \Doctrine\ORM\Query\AST\PathExpression
1104
     */
1105 206
    public function StateFieldPathExpression()
1106
    {
1107 206
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1108
    }
1109
1110
    /**
1111
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1112
     *
1113
     * @return \Doctrine\ORM\Query\AST\PathExpression
1114
     */
1115 9
    public function SingleValuedAssociationPathExpression()
1116
    {
1117 9
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1118
    }
1119
1120
    /**
1121
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1122
     *
1123
     * @return \Doctrine\ORM\Query\AST\PathExpression
1124
     */
1125 23
    public function CollectionValuedPathExpression()
1126
    {
1127 23
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1128
    }
1129
1130
    /**
1131
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1132
     *
1133
     * @return \Doctrine\ORM\Query\AST\SelectClause
1134
     */
1135 775
    public function SelectClause()
1136
    {
1137 775
        $isDistinct = false;
1138 775
        $this->match(Lexer::T_SELECT);
1139
1140
        // Check for DISTINCT
1141 775
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1142 6
            $this->match(Lexer::T_DISTINCT);
1143
1144 6
            $isDistinct = true;
1145
        }
1146
1147
        // Process SelectExpressions (1..N)
1148 775
        $selectExpressions   = [];
1149 775
        $selectExpressions[] = $this->SelectExpression();
1150
1151 767
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1152 297
            $this->match(Lexer::T_COMMA);
1153
1154 297
            $selectExpressions[] = $this->SelectExpression();
1155
        }
1156
1157 766
        return new AST\SelectClause($selectExpressions, $isDistinct);
1158
    }
1159
1160
    /**
1161
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1162
     *
1163
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1164
     */
1165 49
    public function SimpleSelectClause()
1166
    {
1167 49
        $isDistinct = false;
1168 49
        $this->match(Lexer::T_SELECT);
1169
1170 49
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1171
            $this->match(Lexer::T_DISTINCT);
1172
1173
            $isDistinct = true;
1174
        }
1175
1176 49
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1177
    }
1178
1179
    /**
1180
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1181
     *
1182
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1183
     */
1184 31
    public function UpdateClause()
1185
    {
1186 31
        $this->match(Lexer::T_UPDATE);
1187
1188 31
        $token              = $this->lexer->lookahead;
1189 31
        $abstractSchemaName = $this->AbstractSchemaName();
1190
1191 31
        $this->validateAbstractSchemaName($abstractSchemaName);
1192
1193 31
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1194 2
            $this->match(Lexer::T_AS);
1195
        }
1196
1197 31
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1198
1199 31
        $class = $this->em->getClassMetadata($abstractSchemaName);
1200
1201
        // Building queryComponent
1202
        $queryComponent = [
1203 31
            'metadata'     => $class,
1204
            'parent'       => null,
1205
            'relation'     => null,
1206
            'map'          => null,
1207 31
            'nestingLevel' => $this->nestingLevel,
1208 31
            'token'        => $token,
1209
        ];
1210
1211 31
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1212
1213 31
        $this->match(Lexer::T_SET);
1214
1215 31
        $updateItems   = [];
1216 31
        $updateItems[] = $this->UpdateItem();
1217
1218 31
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1219 4
            $this->match(Lexer::T_COMMA);
1220
1221 4
            $updateItems[] = $this->UpdateItem();
1222
        }
1223
1224 31
        $updateClause                              = new AST\UpdateClause($abstractSchemaName, $updateItems);
1225 31
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1226
1227 31
        return $updateClause;
1228
    }
1229
1230
    /**
1231
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1232
     *
1233
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1234
     */
1235 41
    public function DeleteClause()
1236
    {
1237 41
        $this->match(Lexer::T_DELETE);
1238
1239 41
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1240 8
            $this->match(Lexer::T_FROM);
1241
        }
1242
1243 41
        $token              = $this->lexer->lookahead;
1244 41
        $abstractSchemaName = $this->AbstractSchemaName();
1245
1246 41
        $this->validateAbstractSchemaName($abstractSchemaName);
1247
1248 41
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1249
1250 41
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1251 1
            $this->match(Lexer::T_AS);
1252
        }
1253
1254 41
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1255
1256 40
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1257 40
        $class                                     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1258
1259
        // Building queryComponent
1260
        $queryComponent = [
1261 40
            'metadata'     => $class,
1262
            'parent'       => null,
1263
            'relation'     => null,
1264
            'map'          => null,
1265 40
            'nestingLevel' => $this->nestingLevel,
1266 40
            'token'        => $token,
1267
        ];
1268
1269 40
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1270
1271 40
        return $deleteClause;
1272
    }
1273
1274
    /**
1275
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1276
     *
1277
     * @return \Doctrine\ORM\Query\AST\FromClause
1278
     */
1279 766
    public function FromClause()
1280
    {
1281 766
        $this->match(Lexer::T_FROM);
1282
1283 761
        $identificationVariableDeclarations   = [];
1284 761
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1285
1286 748
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1287 7
            $this->match(Lexer::T_COMMA);
1288
1289 7
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1290
        }
1291
1292 748
        return new AST\FromClause($identificationVariableDeclarations);
1293
    }
1294
1295
    /**
1296
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1297
     *
1298
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1299
     */
1300 49
    public function SubselectFromClause()
1301
    {
1302 49
        $this->match(Lexer::T_FROM);
1303
1304 49
        $identificationVariables   = [];
1305 49
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1306
1307 48
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1308
            $this->match(Lexer::T_COMMA);
1309
1310
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1311
        }
1312
1313 48
        return new AST\SubselectFromClause($identificationVariables);
1314
    }
1315
1316
    /**
1317
     * WhereClause ::= "WHERE" ConditionalExpression
1318
     *
1319
     * @return \Doctrine\ORM\Query\AST\WhereClause
1320
     */
1321 341
    public function WhereClause()
1322
    {
1323 341
        $this->match(Lexer::T_WHERE);
1324
1325 341
        return new AST\WhereClause($this->ConditionalExpression());
1326
    }
1327
1328
    /**
1329
     * HavingClause ::= "HAVING" ConditionalExpression
1330
     *
1331
     * @return \Doctrine\ORM\Query\AST\HavingClause
1332
     */
1333 21
    public function HavingClause()
1334
    {
1335 21
        $this->match(Lexer::T_HAVING);
1336
1337 21
        return new AST\HavingClause($this->ConditionalExpression());
1338
    }
1339
1340
    /**
1341
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1342
     *
1343
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1344
     */
1345 33
    public function GroupByClause()
1346
    {
1347 33
        $this->match(Lexer::T_GROUP);
1348 33
        $this->match(Lexer::T_BY);
1349
1350 33
        $groupByItems = [$this->GroupByItem()];
1351
1352 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1353 8
            $this->match(Lexer::T_COMMA);
1354
1355 8
            $groupByItems[] = $this->GroupByItem();
1356
        }
1357
1358 32
        return new AST\GroupByClause($groupByItems);
1359
    }
1360
1361
    /**
1362
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1363
     *
1364
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1365
     */
1366 181
    public function OrderByClause()
1367
    {
1368 181
        $this->match(Lexer::T_ORDER);
1369 181
        $this->match(Lexer::T_BY);
1370
1371 181
        $orderByItems   = [];
1372 181
        $orderByItems[] = $this->OrderByItem();
1373
1374 181
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1375 15
            $this->match(Lexer::T_COMMA);
1376
1377 15
            $orderByItems[] = $this->OrderByItem();
1378
        }
1379
1380 181
        return new AST\OrderByClause($orderByItems);
1381
    }
1382
1383
    /**
1384
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1385
     *
1386
     * @return \Doctrine\ORM\Query\AST\Subselect
1387
     */
1388 49
    public function Subselect()
1389
    {
1390
        // Increase query nesting level
1391 49
        $this->nestingLevel++;
1392
1393 49
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1394
1395 48
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1396 48
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1397 48
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1398 48
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1399
1400
        // Decrease query nesting level
1401 48
        $this->nestingLevel--;
1402
1403 48
        return $subselect;
1404
    }
1405
1406
    /**
1407
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1408
     *
1409
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1410
     */
1411 31
    public function UpdateItem()
1412
    {
1413 31
        $pathExpr = $this->SingleValuedPathExpression();
1414
1415 31
        $this->match(Lexer::T_EQUALS);
1416
1417 31
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1418
1419 31
        return $updateItem;
1420
    }
1421
1422
    /**
1423
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1424
     *
1425
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1426
     */
1427 33
    public function GroupByItem()
1428
    {
1429
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1430 33
        $glimpse = $this->lexer->glimpse();
1431
1432 33
        if ($glimpse['type'] === Lexer::T_DOT) {
1433 14
            return $this->SingleValuedPathExpression();
1434
        }
1435
1436
        // Still need to decide between IdentificationVariable or ResultVariable
1437 19
        $lookaheadValue = $this->lexer->lookahead['value'];
1438
1439 19
        if (! isset($this->queryComponents[$lookaheadValue])) {
1440 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1441
        }
1442
1443 18
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1444 16
            ? $this->IdentificationVariable()
1445 18
            : $this->ResultVariable();
1446
    }
1447
1448
    /**
1449
     * OrderByItem ::= (
1450
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1451
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1452
     * ) ["ASC" | "DESC"]
1453
     *
1454
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1455
     */
1456 181
    public function OrderByItem()
1457
    {
1458 181
        $this->lexer->peek(); // lookahead => '.'
1459 181
        $this->lexer->peek(); // lookahead => token after '.'
1460
1461 181
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1462
1463 181
        $this->lexer->resetPeek();
1464
1465 181
        $glimpse = $this->lexer->glimpse();
1466
1467
        switch (true) {
1468 181
            case ($this->isFunction()):
1469 2
                $expr = $this->FunctionDeclaration();
1470 2
                break;
1471
1472 179
            case ($this->isMathOperator($peek)):
1473 25
                $expr = $this->SimpleArithmeticExpression();
1474 25
                break;
1475
1476 155
            case ($glimpse['type'] === Lexer::T_DOT):
1477 140
                $expr = $this->SingleValuedPathExpression();
1478 140
                break;
1479
1480 19
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1481 2
                $expr = $this->ScalarExpression();
1482 2
                break;
1483
1484
            default:
1485 17
                $expr = $this->ResultVariable();
1486 17
                break;
1487
        }
1488
1489 181
        $type = 'ASC';
1490 181
        $item = new AST\OrderByItem($expr);
1491
1492
        switch (true) {
1493 181
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1494 95
                $this->match(Lexer::T_DESC);
1495 95
                $type = 'DESC';
1496 95
                break;
1497
1498 153
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1499 97
                $this->match(Lexer::T_ASC);
1500 97
                break;
1501
1502
            default:
1503
                // Do nothing
1504
        }
1505
1506 181
        $item->type = $type;
1507
1508 181
        return $item;
1509
    }
1510
1511
    /**
1512
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1513
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1514
     *
1515
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1516
     * grammar that needs to be supported:
1517
     *
1518
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1519
     *
1520
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1521
     *
1522
     * @return AST\ArithmeticExpression
1523
     */
1524 31
    public function NewValue()
1525
    {
1526 31
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1527 1
            $this->match(Lexer::T_NULL);
1528
1529 1
            return null;
1530
        }
1531
1532 30
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1533 17
            $this->match(Lexer::T_INPUT_PARAMETER);
1534
1535 17
            return new AST\InputParameter($this->lexer->token['value']);
1536
        }
1537
1538 13
        return $this->ArithmeticExpression();
1539
    }
1540
1541
    /**
1542
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1543
     *
1544
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1545
     */
1546 763
    public function IdentificationVariableDeclaration()
1547
    {
1548 763
        $joins                    = [];
1549 763
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1550 753
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1551 7
            ? $this->IndexBy()
1552 753
            : null;
1553
1554 753
        $rangeVariableDeclaration->isRoot = true;
1555
1556 753
        while ($this->lexer->isNextToken(Lexer::T_LEFT) ||
1557 753
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1558 753
            $this->lexer->isNextToken(Lexer::T_JOIN)
1559
        ) {
1560 279
            $joins[] = $this->Join();
1561
        }
1562
1563 750
        return new AST\IdentificationVariableDeclaration(
1564 750
            $rangeVariableDeclaration,
1565 750
            $indexBy,
1566 750
            $joins
1567
        );
1568
    }
1569
1570
    /**
1571
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1572
     *
1573
     * {@internal WARNING: Solution is harder than a bare implementation.
1574
     * Desired EBNF support:
1575
     *
1576
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1577
     *
1578
     * It demands that entire SQL generation to become programmatical. This is
1579
     * needed because association based subselect requires "WHERE" conditional
1580
     * expressions to be injected, but there is no scope to do that. Only scope
1581
     * accessible is "FROM", prohibiting an easy implementation without larger
1582
     * changes. }}
1583
     *
1584
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1585
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1586
     */
1587 49
    public function SubselectIdentificationVariableDeclaration()
1588
    {
1589
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1590
        NOT YET IMPLEMENTED!
1591
1592
        $glimpse = $this->lexer->glimpse();
1593
1594
        if ($glimpse['type'] == Lexer::T_DOT) {
1595
            $associationPathExpression = $this->AssociationPathExpression();
1596
1597
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1598
                $this->match(Lexer::T_AS);
1599
            }
1600
1601
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1602
            $identificationVariable      = $associationPathExpression->identificationVariable;
1603
            $field                       = $associationPathExpression->associationField;
1604
1605
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1606
            $association = $class->getProperty($field);
1607
            $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
1608
1609
            // Building queryComponent
1610
            $joinQueryComponent = array(
1611
                'metadata'     => $targetClass,
1612
                'parent'       => $identificationVariable,
1613
                'relation'     => $association,
1614
                'map'          => null,
1615
                'nestingLevel' => $this->nestingLevel,
1616
                'token'        => $this->lexer->lookahead
1617
            );
1618
1619
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1620
1621
            return new AST\SubselectIdentificationVariableDeclaration(
1622
                $associationPathExpression, $aliasIdentificationVariable
1623
            );
1624
        }
1625
        */
1626
1627 49
        return $this->IdentificationVariableDeclaration();
1628
    }
1629
1630
    /**
1631
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1632
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1633
     *          ["WITH" ConditionalExpression]
1634
     *
1635
     * @return \Doctrine\ORM\Query\AST\Join
1636
     */
1637 279
    public function Join()
1638
    {
1639
        // Check Join type
1640 279
        $joinType = AST\Join::JOIN_TYPE_INNER;
1641
1642
        switch (true) {
1643 279
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1644 68
                $this->match(Lexer::T_LEFT);
1645
1646 68
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1647
1648
                // Possible LEFT OUTER join
1649 68
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1650
                    $this->match(Lexer::T_OUTER);
1651
1652
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1653
                }
1654 68
                break;
1655
1656 214
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1657 21
                $this->match(Lexer::T_INNER);
1658 21
                break;
1659
1660
            default:
1661
                // Do nothing
1662
        }
1663
1664 279
        $this->match(Lexer::T_JOIN);
1665
1666 279
        $next            = $this->lexer->glimpse();
1667 279
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1668 276
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1669 276
        $join            = new AST\Join($joinType, $joinDeclaration);
1670
1671
        // Describe non-root join declaration
1672 276
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1673 22
            $joinDeclaration->isRoot = false;
1674
        }
1675
1676
        // Check for ad-hoc Join conditions
1677 276
        if ($adhocConditions) {
1678 25
            $this->match(Lexer::T_WITH);
1679
1680 25
            $join->conditionalExpression = $this->ConditionalExpression();
1681
        }
1682
1683 276
        return $join;
1684
    }
1685
1686
    /**
1687
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1688
     *
1689
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1690
     *
1691
     * @throws QueryException
1692
     */
1693 763
    public function RangeVariableDeclaration()
1694
    {
1695 763
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
1696 2
            $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1697
        }
1698
1699 762
        $abstractSchemaName = $this->AbstractSchemaName();
1700
1701 761
        $this->validateAbstractSchemaName($abstractSchemaName);
1702
1703 753
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1704 6
            $this->match(Lexer::T_AS);
1705
        }
1706
1707 753
        $token                       = $this->lexer->lookahead;
1708 753
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1709 753
        $classMetadata               = $this->em->getClassMetadata($abstractSchemaName);
1710
1711
        // Building queryComponent
1712
        $queryComponent = [
1713 753
            'metadata'     => $classMetadata,
1714
            'parent'       => null,
1715
            'relation'     => null,
1716
            'map'          => null,
1717 753
            'nestingLevel' => $this->nestingLevel,
1718 753
            'token'        => $token,
1719
        ];
1720
1721 753
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1722
1723 753
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1724
    }
1725
1726
    /**
1727
     * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1728
     *
1729
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1730
     */
1731 257
    public function JoinAssociationDeclaration()
1732
    {
1733 257
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1734
1735 257
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1736 5
            $this->match(Lexer::T_AS);
1737
        }
1738
1739 257
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1740 255
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1741
1742 255
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1743 255
        $field                  = $joinAssociationPathExpression->associationField;
1744
1745 255
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1746 255
        $association = $class->getProperty($field);
1747 255
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
1748
1749
        // Building queryComponent
1750
        $joinQueryComponent = [
1751 255
            'metadata'     => $targetClass,
1752 255
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1753 255
            'relation'     => $association,
1754
            'map'          => null,
1755 255
            'nestingLevel' => $this->nestingLevel,
1756 255
            'token'        => $this->lexer->lookahead,
1757
        ];
1758
1759 255
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1760
1761 255
        return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
1762
    }
1763
1764
    /**
1765
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1766
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1767
     *
1768
     * @return \Doctrine\ORM\Query\AST\PartialObjectExpression
1769
     */
1770 9
    public function PartialObjectExpression()
1771
    {
1772 9
        $this->match(Lexer::T_PARTIAL);
1773
1774 9
        $partialFieldSet = [];
1775
1776 9
        $identificationVariable = $this->IdentificationVariable();
1777
1778 9
        $this->match(Lexer::T_DOT);
1779 9
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1780 9
        $this->match(Lexer::T_IDENTIFIER);
1781
1782 9
        $field = $this->lexer->token['value'];
1783
1784
        // First field in partial expression might be embeddable property
1785 9
        while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1786
            $this->match(Lexer::T_DOT);
1787
            $this->match(Lexer::T_IDENTIFIER);
1788
            $field .= '.' . $this->lexer->token['value'];
1789
        }
1790
1791 9
        $partialFieldSet[] = $field;
1792
1793 9
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1794 7
            $this->match(Lexer::T_COMMA);
1795 7
            $this->match(Lexer::T_IDENTIFIER);
1796
1797 7
            $field = $this->lexer->token['value'];
1798
1799 7
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1800
                $this->match(Lexer::T_DOT);
1801
                $this->match(Lexer::T_IDENTIFIER);
1802
                $field .= '.' . $this->lexer->token['value'];
1803
            }
1804
1805 7
            $partialFieldSet[] = $field;
1806
        }
1807
1808 9
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1809
1810 9
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1811
1812
        // Defer PartialObjectExpression validation
1813 9
        $this->deferredPartialObjectExpressions[] = [
1814 9
            'expression'   => $partialObjectExpression,
1815 9
            'nestingLevel' => $this->nestingLevel,
1816 9
            'token'        => $this->lexer->token,
1817
        ];
1818
1819 9
        return $partialObjectExpression;
1820
    }
1821
1822
    /**
1823
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1824
     *
1825
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1826
     */
1827 27
    public function NewObjectExpression()
1828
    {
1829 27
        $this->match(Lexer::T_NEW);
1830
1831 27
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1832 27
        $token     = $this->lexer->token;
1833
1834 27
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1835
1836 27
        $args[] = $this->NewObjectArg();
1837
1838 27
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1839 23
            $this->match(Lexer::T_COMMA);
1840
1841 23
            $args[] = $this->NewObjectArg();
1842
        }
1843
1844 27
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1845
1846 27
        $expression = new AST\NewObjectExpression($className, $args);
1847
1848
        // Defer NewObjectExpression validation
1849 27
        $this->deferredNewObjectExpressions[] = [
1850 27
            'token'        => $token,
1851 27
            'expression'   => $expression,
1852 27
            'nestingLevel' => $this->nestingLevel,
1853
        ];
1854
1855 27
        return $expression;
1856
    }
1857
1858
    /**
1859
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1860
     *
1861
     * @return mixed
1862
     */
1863 27
    public function NewObjectArg()
1864
    {
1865 27
        $token = $this->lexer->lookahead;
1866 27
        $peek  = $this->lexer->glimpse();
1867
1868 27
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1869 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1870 2
            $expression = $this->Subselect();
1871 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1872
1873 2
            return $expression;
1874
        }
1875
1876 27
        if ($token['type'] === Lexer::T_NEW) {
1877 1
            $expression = $this->NewObjectExpression();
1878
1879 1
            return $expression;
1880
        }
1881
1882 27
        return $this->ScalarExpression();
1883
    }
1884
1885
    /**
1886
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1887
     *
1888
     * @return \Doctrine\ORM\Query\AST\IndexBy
1889
     */
1890 11
    public function IndexBy()
1891
    {
1892 11
        $this->match(Lexer::T_INDEX);
1893 11
        $this->match(Lexer::T_BY);
1894 11
        $pathExpr = $this->StateFieldPathExpression();
1895
1896
        // Add the INDEX BY info to the query component
1897 11
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1898
1899 11
        return new AST\IndexBy($pathExpr);
1900
    }
1901
1902
    /**
1903
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1904
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1905
     *                      InstanceOfExpression
1906
     *
1907
     * @return mixed One of the possible expressions or subexpressions.
1908
     */
1909 164
    public function ScalarExpression()
1910
    {
1911 164
        $lookahead = $this->lexer->lookahead['type'];
1912 164
        $peek      = $this->lexer->glimpse();
1913
1914 164
        switch (true) {
1915
            case ($lookahead === Lexer::T_INTEGER):
1916 161
            case ($lookahead === Lexer::T_FLOAT):
1917
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1918 161
            case ($lookahead === Lexer::T_MINUS):
1919 161
            case ($lookahead === Lexer::T_PLUS):
1920 17
                return $this->SimpleArithmeticExpression();
1921
1922 161
            case ($lookahead === Lexer::T_STRING):
1923 13
                return $this->StringPrimary();
1924
1925 159
            case ($lookahead === Lexer::T_TRUE):
1926 159
            case ($lookahead === Lexer::T_FALSE):
1927 3
                $this->match($lookahead);
1928
1929 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1930
1931 159
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
1932
                switch (true) {
1933 1
                    case $this->isMathOperator($peek):
1934
                        // :param + u.value
1935 1
                        return $this->SimpleArithmeticExpression();
1936
                    default:
1937
                        return $this->InputParameter();
1938
                }
1939
                // cannot get here
1940
1941 159
            case ($lookahead === Lexer::T_CASE):
1942 155
            case ($lookahead === Lexer::T_COALESCE):
1943 155
            case ($lookahead === Lexer::T_NULLIF):
1944
                // Since NULLIF and COALESCE can be identified as a function,
1945
                // we need to check these before checking for FunctionDeclaration
1946 8
                return $this->CaseExpression();
1947
1948 155
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1949 4
                return $this->SimpleArithmeticExpression();
1950
1951
            // this check must be done before checking for a filed path expression
1952 152
            case ($this->isFunction()):
1953 27
                $this->lexer->peek(); // "("
1954
1955
                switch (true) {
1956 27
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1957
                        // SUM(u.id) + COUNT(u.id)
1958 7
                        return $this->SimpleArithmeticExpression();
1959
1960
                    default:
1961
                        // IDENTITY(u)
1962 22
                        return $this->FunctionDeclaration();
1963
                }
1964
1965
                break;
1966
            // it is no function, so it must be a field path
1967 133
            case ($lookahead === Lexer::T_IDENTIFIER):
1968 133
                $this->lexer->peek(); // lookahead => '.'
1969 133
                $this->lexer->peek(); // lookahead => token after '.'
1970 133
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1971 133
                $this->lexer->resetPeek();
1972
1973 133
                if ($this->isMathOperator($peek)) {
1974 7
                    return $this->SimpleArithmeticExpression();
1975
                }
1976
1977 128
                return $this->StateFieldPathExpression();
1978
1979
            default:
1980
                $this->syntaxError();
1981
        }
1982
    }
1983
1984
    /**
1985
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
1986
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1987
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1988
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1989
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1990
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1991
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1992
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1993
     *
1994
     * @return mixed One of the possible expressions or subexpressions.
1995
     */
1996 19
    public function CaseExpression()
1997
    {
1998 19
        $lookahead = $this->lexer->lookahead['type'];
1999
2000 19
        switch ($lookahead) {
2001
            case Lexer::T_NULLIF:
2002 5
                return $this->NullIfExpression();
2003
2004
            case Lexer::T_COALESCE:
2005 2
                return $this->CoalesceExpression();
2006
2007
            case Lexer::T_CASE:
2008 14
                $this->lexer->resetPeek();
2009 14
                $peek = $this->lexer->peek();
2010
2011 14
                if ($peek['type'] === Lexer::T_WHEN) {
2012 9
                    return $this->GeneralCaseExpression();
2013
                }
2014
2015 5
                return $this->SimpleCaseExpression();
2016
2017
            default:
2018
                // Do nothing
2019
                break;
2020
        }
2021
2022
        $this->syntaxError();
2023
    }
2024
2025
    /**
2026
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2027
     *
2028
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2029
     */
2030 3
    public function CoalesceExpression()
2031
    {
2032 3
        $this->match(Lexer::T_COALESCE);
2033 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2034
2035
        // Process ScalarExpressions (1..N)
2036 3
        $scalarExpressions   = [];
2037 3
        $scalarExpressions[] = $this->ScalarExpression();
2038
2039 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2040 3
            $this->match(Lexer::T_COMMA);
2041
2042 3
            $scalarExpressions[] = $this->ScalarExpression();
2043
        }
2044
2045 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2046
2047 3
        return new AST\CoalesceExpression($scalarExpressions);
2048
    }
2049
2050
    /**
2051
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2052
     *
2053
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2054
     */
2055 5
    public function NullIfExpression()
2056
    {
2057 5
        $this->match(Lexer::T_NULLIF);
2058 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2059
2060 5
        $firstExpression = $this->ScalarExpression();
2061 5
        $this->match(Lexer::T_COMMA);
2062 5
        $secondExpression = $this->ScalarExpression();
2063
2064 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2065
2066 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2067
    }
2068
2069
    /**
2070
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2071
     *
2072
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2073
     */
2074 9
    public function GeneralCaseExpression()
2075
    {
2076 9
        $this->match(Lexer::T_CASE);
2077
2078
        // Process WhenClause (1..N)
2079 9
        $whenClauses = [];
2080
2081
        do {
2082 9
            $whenClauses[] = $this->WhenClause();
2083 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2084
2085 9
        $this->match(Lexer::T_ELSE);
2086 9
        $scalarExpression = $this->ScalarExpression();
2087 9
        $this->match(Lexer::T_END);
2088
2089 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2090
    }
2091
2092
    /**
2093
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2094
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2095
     *
2096
     * @return AST\SimpleCaseExpression
2097
     */
2098 5
    public function SimpleCaseExpression()
2099
    {
2100 5
        $this->match(Lexer::T_CASE);
2101 5
        $caseOperand = $this->StateFieldPathExpression();
2102
2103
        // Process SimpleWhenClause (1..N)
2104 5
        $simpleWhenClauses = [];
2105
2106
        do {
2107 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2108 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2109
2110 5
        $this->match(Lexer::T_ELSE);
2111 5
        $scalarExpression = $this->ScalarExpression();
2112 5
        $this->match(Lexer::T_END);
2113
2114 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2115
    }
2116
2117
    /**
2118
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2119
     *
2120
     * @return \Doctrine\ORM\Query\AST\WhenClause
2121
     */
2122 9
    public function WhenClause()
2123
    {
2124 9
        $this->match(Lexer::T_WHEN);
2125 9
        $conditionalExpression = $this->ConditionalExpression();
2126 9
        $this->match(Lexer::T_THEN);
2127
2128 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2129
    }
2130
2131
    /**
2132
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2133
     *
2134
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2135
     */
2136 5
    public function SimpleWhenClause()
2137
    {
2138 5
        $this->match(Lexer::T_WHEN);
2139 5
        $conditionalExpression = $this->ScalarExpression();
2140 5
        $this->match(Lexer::T_THEN);
2141
2142 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2143
    }
2144
2145
    /**
2146
     * SelectExpression ::= (
2147
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2148
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2149
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2150
     *
2151
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2152
     */
2153 775
    public function SelectExpression()
2154
    {
2155 775
        $expression    = null;
2156 775
        $identVariable = null;
2157 775
        $peek          = $this->lexer->glimpse();
2158 775
        $lookaheadType = $this->lexer->lookahead['type'];
2159
2160 775
        switch (true) {
2161
            // ScalarExpression (u.name)
2162 699
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2163 107
                $expression = $this->ScalarExpression();
2164 107
                break;
2165
2166
            // IdentificationVariable (u)
2167 715
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2168 589
                $expression = $identVariable = $this->IdentificationVariable();
2169 589
                break;
2170
2171
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2172 188
            case ($lookaheadType === Lexer::T_CASE):
2173 183
            case ($lookaheadType === Lexer::T_COALESCE):
2174 181
            case ($lookaheadType === Lexer::T_NULLIF):
2175 9
                $expression = $this->CaseExpression();
2176 9
                break;
2177
2178
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2179 179
            case ($this->isFunction()):
2180 102
                $this->lexer->peek(); // "("
2181
2182
                switch (true) {
2183 102
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2184
                        // SUM(u.id) + COUNT(u.id)
2185 2
                        $expression = $this->ScalarExpression();
2186 2
                        break;
2187
2188
                    default:
2189
                        // IDENTITY(u)
2190 100
                        $expression = $this->FunctionDeclaration();
2191 100
                        break;
2192
                }
2193
2194 102
                break;
2195
2196
            // PartialObjectExpression (PARTIAL u.{id, name})
2197 78
            case ($lookaheadType === Lexer::T_PARTIAL):
2198 9
                $expression    = $this->PartialObjectExpression();
2199 9
                $identVariable = $expression->identificationVariable;
2200 9
                break;
2201
2202
            // Subselect
2203 69
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2204 23
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2205 23
                $expression = $this->Subselect();
2206 23
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2207 23
                break;
2208
2209
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2210 46
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2211 42
            case ($lookaheadType === Lexer::T_INTEGER):
2212 40
            case ($lookaheadType === Lexer::T_STRING):
2213 31
            case ($lookaheadType === Lexer::T_FLOAT):
2214
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2215 31
            case ($lookaheadType === Lexer::T_MINUS):
2216 31
            case ($lookaheadType === Lexer::T_PLUS):
2217 16
                $expression = $this->SimpleArithmeticExpression();
2218 16
                break;
2219
2220
            // NewObjectExpression (New ClassName(id, name))
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2221 30
            case ($lookaheadType === Lexer::T_NEW):
2222 27
                $expression = $this->NewObjectExpression();
2223 27
                break;
2224
2225
            default:
2226 3
                $this->syntaxError(
2227 3
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2228 3
                    $this->lexer->lookahead
2229
                );
2230
        }
2231
2232
        // [["AS"] ["HIDDEN"] AliasResultVariable]
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2233 772
        $mustHaveAliasResultVariable = false;
2234
2235 772
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2236 122
            $this->match(Lexer::T_AS);
2237
2238 122
            $mustHaveAliasResultVariable = true;
2239
        }
2240
2241 772
        $hiddenAliasResultVariable = false;
2242
2243 772
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2244 10
            $this->match(Lexer::T_HIDDEN);
2245
2246 10
            $hiddenAliasResultVariable = true;
2247
        }
2248
2249 772
        $aliasResultVariable = null;
2250
2251 772
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2252 131
            $token               = $this->lexer->lookahead;
2253 131
            $aliasResultVariable = $this->AliasResultVariable();
2254
2255
            // Include AliasResultVariable in query components.
2256 126
            $this->queryComponents[$aliasResultVariable] = [
2257 126
                'resultVariable' => $expression,
2258 126
                'nestingLevel'   => $this->nestingLevel,
2259 126
                'token'          => $token,
2260
            ];
2261
        }
2262
2263
        // AST
2264
2265 767
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2266
2267 767
        if ($identVariable) {
2268 596
            $this->identVariableExpressions[$identVariable] = $expr;
2269
        }
2270
2271 767
        return $expr;
2272
    }
2273
2274
    /**
2275
     * SimpleSelectExpression ::= (
2276
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2277
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2278
     * ) [["AS"] AliasResultVariable]
2279
     *
2280
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2281
     */
2282 49
    public function SimpleSelectExpression()
2283
    {
2284 49
        $peek = $this->lexer->glimpse();
2285
2286 49
        switch ($this->lexer->lookahead['type']) {
2287
            case Lexer::T_IDENTIFIER:
2288 19
                switch (true) {
2289 19
                    case ($peek['type'] === Lexer::T_DOT):
2290 16
                        $expression = $this->StateFieldPathExpression();
2291
2292 16
                        return new AST\SimpleSelectExpression($expression);
2293
2294 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2295 2
                        $expression = $this->IdentificationVariable();
2296
2297 2
                        return new AST\SimpleSelectExpression($expression);
2298
2299 1
                    case ($this->isFunction()):
2300
                        // SUM(u.id) + COUNT(u.id)
2301 1
                        if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
2302
                            return new AST\SimpleSelectExpression($this->ScalarExpression());
2303
                        }
2304
                        // COUNT(u.id)
2305 1
                        if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
2306
                            return new AST\SimpleSelectExpression($this->AggregateExpression());
2307
                        }
2308
                        // IDENTITY(u)
2309 1
                        return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2310
2311
                    default:
2312
                        // Do nothing
2313
                }
2314
                break;
2315
2316
            case Lexer::T_OPEN_PARENTHESIS:
2317 3
                if ($peek['type'] !== Lexer::T_SELECT) {
2318
                    // Shortcut: ScalarExpression => SimpleArithmeticExpression
2319 3
                    $expression = $this->SimpleArithmeticExpression();
2320
2321 3
                    return new AST\SimpleSelectExpression($expression);
2322
                }
2323
2324
                // Subselect
2325
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2326
                $expression = $this->Subselect();
2327
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2328
2329
                return new AST\SimpleSelectExpression($expression);
2330
2331
            default:
2332
                // Do nothing
2333
        }
2334
2335 28
        $this->lexer->peek();
2336
2337 28
        $expression = $this->ScalarExpression();
2338 28
        $expr       = new AST\SimpleSelectExpression($expression);
2339
2340 28
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2341 1
            $this->match(Lexer::T_AS);
2342
        }
2343
2344 28
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2345 2
            $token                             = $this->lexer->lookahead;
2346 2
            $resultVariable                    = $this->AliasResultVariable();
2347 2
            $expr->fieldIdentificationVariable = $resultVariable;
2348
2349
            // Include AliasResultVariable in query components.
2350 2
            $this->queryComponents[$resultVariable] = [
2351 2
                'resultvariable' => $expr,
2352 2
                'nestingLevel'   => $this->nestingLevel,
2353 2
                'token'          => $token,
2354
            ];
2355
        }
2356
2357 28
        return $expr;
2358
    }
2359
2360
    /**
2361
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2362
     *
2363
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2364
     */
2365 385
    public function ConditionalExpression()
2366
    {
2367 385
        $conditionalTerms   = [];
2368 385
        $conditionalTerms[] = $this->ConditionalTerm();
2369
2370 382
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2371 16
            $this->match(Lexer::T_OR);
2372
2373 16
            $conditionalTerms[] = $this->ConditionalTerm();
2374
        }
2375
2376
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2377
        // if only one AST\ConditionalTerm is defined
2378 382
        if (\count($conditionalTerms) === 1) {
2379 374
            return $conditionalTerms[0];
2380
        }
2381
2382 16
        return new AST\ConditionalExpression($conditionalTerms);
2383
    }
2384
2385
    /**
2386
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2387
     *
2388
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2389
     */
2390 385
    public function ConditionalTerm()
2391
    {
2392 385
        $conditionalFactors   = [];
2393 385
        $conditionalFactors[] = $this->ConditionalFactor();
2394
2395 382
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2396 32
            $this->match(Lexer::T_AND);
2397
2398 32
            $conditionalFactors[] = $this->ConditionalFactor();
2399
        }
2400
2401
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2402
        // if only one AST\ConditionalFactor is defined
2403 382
        if (\count($conditionalFactors) === 1) {
2404 363
            return $conditionalFactors[0];
2405
        }
2406
2407 32
        return new AST\ConditionalTerm($conditionalFactors);
2408
    }
2409
2410
    /**
2411
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2412
     *
2413
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2414
     */
2415 385
    public function ConditionalFactor()
2416
    {
2417 385
        $not = false;
2418
2419 385
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2420 6
            $this->match(Lexer::T_NOT);
2421
2422 6
            $not = true;
2423
        }
2424
2425 385
        $conditionalPrimary = $this->ConditionalPrimary();
2426
2427
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2428
        // if only one AST\ConditionalPrimary is defined
2429 382
        if (! $not) {
2430 380
            return $conditionalPrimary;
2431
        }
2432
2433 6
        $conditionalFactor      = new AST\ConditionalFactor($conditionalPrimary);
2434 6
        $conditionalFactor->not = $not;
2435
2436 6
        return $conditionalFactor;
2437
    }
2438
2439
    /**
2440
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2441
     *
2442
     * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
2443
     */
2444 385
    public function ConditionalPrimary()
2445
    {
2446 385
        $condPrimary = new AST\ConditionalPrimary;
2447
2448 385
        if (! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2449 376
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2450
2451 373
            return $condPrimary;
2452
        }
2453
2454
        // Peek beyond the matching closing parenthesis ')'
2455 25
        $peek = $this->peekBeyondClosingParenthesis();
2456
2457 25
        if (in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!='], true) ||
2458 22
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS], true) ||
2459 25
            $this->isMathOperator($peek)) {
2460 15
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2461
2462 15
            return $condPrimary;
2463
        }
2464
2465 21
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2466 21
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2467 21
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2468
2469 21
        return $condPrimary;
2470
    }
2471
2472
    /**
2473
     * SimpleConditionalExpression ::=
2474
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2475
     *      InExpression | NullComparisonExpression | ExistsExpression |
2476
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2477
     *      InstanceOfExpression
2478
     */
2479 385
    public function SimpleConditionalExpression()
2480
    {
2481 385
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2482 7
            return $this->ExistsExpression();
2483
        }
2484
2485 385
        $token     = $this->lexer->lookahead;
2486 385
        $peek      = $this->lexer->glimpse();
2487 385
        $lookahead = $token;
2488
2489 385
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2490
            $token = $this->lexer->glimpse();
2491
        }
2492
2493 385
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2494
            // Peek beyond the matching closing parenthesis.
2495 361
            $beyond = $this->lexer->peek();
2496
2497 361
            switch ($peek['value']) {
2498
                case '(':
2499
                    // Peeks beyond the matched closing parenthesis.
2500 37
                    $token = $this->peekBeyondClosingParenthesis(false);
2501
2502 37
                    if ($token['type'] === Lexer::T_NOT) {
2503 3
                        $token = $this->lexer->peek();
2504
                    }
2505
2506 37
                    if ($token['type'] === Lexer::T_IS) {
2507 2
                        $lookahead = $this->lexer->peek();
2508
                    }
2509 37
                    break;
2510
2511
                default:
2512
                    // Peek beyond the PathExpression or InputParameter.
2513 333
                    $token = $beyond;
2514
2515 333
                    while ($token['value'] === '.') {
2516 288
                        $this->lexer->peek();
2517
2518 288
                        $token = $this->lexer->peek();
2519
                    }
2520
2521
                    // Also peek beyond a NOT if there is one.
2522 333
                    if ($token['type'] === Lexer::T_NOT) {
2523 11
                        $token = $this->lexer->peek();
2524
                    }
2525
2526
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2527 333
                    $lookahead = $this->lexer->peek();
2528
            }
2529
2530
            // Also peek beyond a NOT if there is one.
2531 361
            if ($lookahead['type'] === Lexer::T_NOT) {
2532 7
                $lookahead = $this->lexer->peek();
2533
            }
2534
2535 361
            $this->lexer->resetPeek();
2536
        }
2537
2538 385
        if ($token['type'] === Lexer::T_BETWEEN) {
2539 8
            return $this->BetweenExpression();
2540
        }
2541
2542 379
        if ($token['type'] === Lexer::T_LIKE) {
2543 14
            return $this->LikeExpression();
2544
        }
2545
2546 366
        if ($token['type'] === Lexer::T_IN) {
2547 35
            return $this->InExpression();
2548
        }
2549
2550 340
        if ($token['type'] === Lexer::T_INSTANCE) {
2551 17
            return $this->InstanceOfExpression();
2552
        }
2553
2554 323
        if ($token['type'] === Lexer::T_MEMBER) {
2555 8
            return $this->CollectionMemberExpression();
2556
        }
2557
2558 315
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2559 13
            return $this->NullComparisonExpression();
2560
        }
2561
2562 305
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_EMPTY) {
2563 4
            return $this->EmptyCollectionComparisonExpression();
2564
        }
2565
2566 301
        return $this->ComparisonExpression();
2567
    }
2568
2569
    /**
2570
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2571
     *
2572
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2573
     */
2574 4
    public function EmptyCollectionComparisonExpression()
2575
    {
2576 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2577 4
            $this->CollectionValuedPathExpression()
2578
        );
2579 4
        $this->match(Lexer::T_IS);
2580
2581 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2582 2
            $this->match(Lexer::T_NOT);
2583 2
            $emptyCollectionCompExpr->not = true;
2584
        }
2585
2586 4
        $this->match(Lexer::T_EMPTY);
2587
2588 4
        return $emptyCollectionCompExpr;
2589
    }
2590
2591
    /**
2592
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2593
     *
2594
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2595
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2596
     *
2597
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2598
     */
2599 8
    public function CollectionMemberExpression()
2600
    {
2601 8
        $not        = false;
2602 8
        $entityExpr = $this->EntityExpression();
2603
2604 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2605
            $this->match(Lexer::T_NOT);
2606
2607
            $not = true;
2608
        }
2609
2610 8
        $this->match(Lexer::T_MEMBER);
2611
2612 8
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2613 8
            $this->match(Lexer::T_OF);
2614
        }
2615
2616 8
        $collMemberExpr      = new AST\CollectionMemberExpression(
2617 8
            $entityExpr,
2618 8
            $this->CollectionValuedPathExpression()
2619
        );
2620 8
        $collMemberExpr->not = $not;
2621
2622 8
        return $collMemberExpr;
2623
    }
2624
2625
    /**
2626
     * Literal ::= string | char | integer | float | boolean
2627
     *
2628
     * @return \Doctrine\ORM\Query\AST\Literal
2629
     */
2630 186
    public function Literal()
2631
    {
2632 186
        switch ($this->lexer->lookahead['type']) {
2633
            case Lexer::T_STRING:
2634 48
                $this->match(Lexer::T_STRING);
2635
2636 48
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2637
            case Lexer::T_INTEGER:
2638
            case Lexer::T_FLOAT:
2639 139
                $this->match(
2640 139
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2641
                );
2642
2643 139
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2644
            case Lexer::T_TRUE:
2645
            case Lexer::T_FALSE:
2646 8
                $this->match(
2647 8
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2648
                );
2649
2650 8
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2651
            default:
2652
                $this->syntaxError('Literal');
2653
        }
2654
    }
2655
2656
    /**
2657
     * InParameter ::= Literal | InputParameter
2658
     *
2659
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2660
     */
2661 26
    public function InParameter()
2662
    {
2663 26
        if ($this->lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) {
2664 14
            return $this->InputParameter();
2665
        }
2666
2667 12
        return $this->Literal();
2668
    }
2669
2670
    /**
2671
     * InputParameter ::= PositionalParameter | NamedParameter
2672
     *
2673
     * @return \Doctrine\ORM\Query\AST\InputParameter
2674
     */
2675 168
    public function InputParameter()
2676
    {
2677 168
        $this->match(Lexer::T_INPUT_PARAMETER);
2678
2679 168
        return new AST\InputParameter($this->lexer->token['value']);
2680
    }
2681
2682
    /**
2683
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2684
     *
2685
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2686
     */
2687 335
    public function ArithmeticExpression()
2688
    {
2689 335
        $expr = new AST\ArithmeticExpression;
2690
2691 335
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2692 19
            $peek = $this->lexer->glimpse();
2693
2694 19
            if ($peek['type'] === Lexer::T_SELECT) {
2695 7
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2696 7
                $expr->subselect = $this->Subselect();
2697 7
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2698
2699 7
                return $expr;
2700
            }
2701
        }
2702
2703 335
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2704
2705 335
        return $expr;
2706
    }
2707
2708
    /**
2709
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2710
     *
2711
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2712
     */
2713 440
    public function SimpleArithmeticExpression()
2714
    {
2715 440
        $terms   = [];
2716 440
        $terms[] = $this->ArithmeticTerm();
2717
2718 440
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2719 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2720
2721 21
            $terms[] = $this->lexer->token['value'];
2722 21
            $terms[] = $this->ArithmeticTerm();
2723
        }
2724
2725
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2726
        // if only one AST\ArithmeticTerm is defined
2727 440
        if (\count($terms) === 1) {
2728 435
            return $terms[0];
2729
        }
2730
2731 21
        return new AST\SimpleArithmeticExpression($terms);
2732
    }
2733
2734
    /**
2735
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2736
     *
2737
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2738
     */
2739 440
    public function ArithmeticTerm()
2740
    {
2741 440
        $factors   = [];
2742 440
        $factors[] = $this->ArithmeticFactor();
2743
2744 440
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2745 53
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2746
2747 53
            $factors[] = $this->lexer->token['value'];
2748 53
            $factors[] = $this->ArithmeticFactor();
2749
        }
2750
2751
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2752
        // if only one AST\ArithmeticFactor is defined
2753 440
        if (\count($factors) === 1) {
2754 412
            return $factors[0];
2755
        }
2756
2757 53
        return new AST\ArithmeticTerm($factors);
2758
    }
2759
2760
    /**
2761
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2762
     *
2763
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2764
     */
2765 440
    public function ArithmeticFactor()
2766
    {
2767 440
        $sign   = null;
2768 440
        $isPlus = $this->lexer->isNextToken(Lexer::T_PLUS);
2769
2770 440
        if ($isPlus || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2771 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2772 3
            $sign = $isPlus;
2773
        }
2774
2775 440
        $primary = $this->ArithmeticPrimary();
2776
2777
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2778
        // if only one AST\ArithmeticPrimary is defined
2779 440
        if ($sign === null) {
2780 439
            return $primary;
2781
        }
2782
2783 3
        return new AST\ArithmeticFactor($primary, $sign);
2784
    }
2785
2786
    /**
2787
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2788
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2789
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2790
     *          | InputParameter | CaseExpression
2791
     */
2792 453
    public function ArithmeticPrimary()
2793
    {
2794 453
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2795 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2796
2797 25
            $expr = $this->SimpleArithmeticExpression();
2798
2799 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2800
2801 25
            return new AST\ParenthesisExpression($expr);
2802
        }
2803
2804 453
        switch ($this->lexer->lookahead['type']) {
2805
            case Lexer::T_COALESCE:
2806
            case Lexer::T_NULLIF:
2807
            case Lexer::T_CASE:
2808 4
                return $this->CaseExpression();
2809
2810
            case Lexer::T_IDENTIFIER:
2811 423
                $peek = $this->lexer->glimpse();
2812
2813 423
                if ($peek['value'] === '(') {
2814 41
                    return $this->FunctionDeclaration();
2815
                }
2816
2817 394
                if ($peek['value'] === '.') {
2818 383
                    return $this->SingleValuedPathExpression();
2819
                }
2820
2821 46
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2822 10
                    return $this->ResultVariable();
2823
                }
2824
2825 38
                return $this->StateFieldPathExpression();
2826
2827
            case Lexer::T_INPUT_PARAMETER:
2828 149
                return $this->InputParameter();
2829
2830
            default:
2831 180
                $peek = $this->lexer->glimpse();
2832
2833 180
                if ($peek['value'] === '(') {
2834 18
                    return $this->FunctionDeclaration();
2835
                }
2836
2837 176
                return $this->Literal();
2838
        }
2839
    }
2840
2841
    /**
2842
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2843
     *
2844
     * @return \Doctrine\ORM\Query\AST\Subselect |
2845
     *         string
2846
     */
2847 14
    public function StringExpression()
2848
    {
2849 14
        $peek = $this->lexer->glimpse();
2850
2851
        // Subselect
2852 14
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2853
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2854
            $expr = $this->Subselect();
2855
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2856
2857
            return $expr;
2858
        }
2859
2860
        // ResultVariable (string)
2861 14
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2862 14
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2863 2
            return $this->ResultVariable();
2864
        }
2865
2866 12
        return $this->StringPrimary();
2867
    }
2868
2869
    /**
2870
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2871
     */
2872 63
    public function StringPrimary()
2873
    {
2874 63
        $lookaheadType = $this->lexer->lookahead['type'];
2875
2876 63
        switch ($lookaheadType) {
2877
            case Lexer::T_IDENTIFIER:
2878 35
                $peek = $this->lexer->glimpse();
2879
2880 35
                if ($peek['value'] === '.') {
2881 35
                    return $this->StateFieldPathExpression();
2882
                }
2883
2884 8
                if ($peek['value'] === '(') {
2885
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2886 8
                    return $this->FunctionDeclaration();
2887
                }
2888
2889
                $this->syntaxError("'.' or '('");
2890
                break;
2891
2892
            case Lexer::T_STRING:
2893 45
                $this->match(Lexer::T_STRING);
2894
2895 45
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2896
2897
            case Lexer::T_INPUT_PARAMETER:
2898 2
                return $this->InputParameter();
2899
2900
            case Lexer::T_CASE:
2901
            case Lexer::T_COALESCE:
2902
            case Lexer::T_NULLIF:
2903
                return $this->CaseExpression();
2904
        }
2905
2906
        $this->syntaxError(
2907
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2908
        );
2909
    }
2910
2911
    /**
2912
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2913
     *
2914
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2915
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2916
     */
2917 8
    public function EntityExpression()
2918
    {
2919 8
        $glimpse = $this->lexer->glimpse();
2920
2921 8
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2922 1
            return $this->SingleValuedAssociationPathExpression();
2923
        }
2924
2925 7
        return $this->SimpleEntityExpression();
2926
    }
2927
2928
    /**
2929
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2930
     *
2931
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2932
     */
2933 7
    public function SimpleEntityExpression()
2934
    {
2935 7
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2936 5
            return $this->InputParameter();
2937
        }
2938
2939 2
        return $this->StateFieldPathExpression();
2940
    }
2941
2942
    /**
2943
     * AggregateExpression ::=
2944
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2945
     *
2946
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2947
     */
2948 88
    public function AggregateExpression()
2949
    {
2950 88
        $lookaheadType = $this->lexer->lookahead['type'];
2951 88
        $isDistinct    = false;
2952
2953 88
        if (! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM], true)) {
2954
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2955
        }
2956
2957 88
        $this->match($lookaheadType);
2958 88
        $functionName = $this->lexer->token['value'];
2959 88
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2960
2961 88
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2962 3
            $this->match(Lexer::T_DISTINCT);
2963 3
            $isDistinct = true;
2964
        }
2965
2966 88
        $pathExp = $this->SimpleArithmeticExpression();
2967
2968 88
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2969
2970 88
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2971
    }
2972
2973
    /**
2974
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2975
     *
2976
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2977
     */
2978 3
    public function QuantifiedExpression()
2979
    {
2980 3
        $lookaheadType = $this->lexer->lookahead['type'];
2981 3
        $value         = $this->lexer->lookahead['value'];
2982
2983 3
        if (! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME], true)) {
2984
            $this->syntaxError('ALL, ANY or SOME');
2985
        }
2986
2987 3
        $this->match($lookaheadType);
2988 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2989
2990 3
        $qExpr       = new AST\QuantifiedExpression($this->Subselect());
2991 3
        $qExpr->type = $value;
2992
2993 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2994
2995 3
        return $qExpr;
2996
    }
2997
2998
    /**
2999
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
3000
     *
3001
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
3002
     */
3003 8
    public function BetweenExpression()
3004
    {
3005 8
        $not        = false;
3006 8
        $arithExpr1 = $this->ArithmeticExpression();
3007
3008 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3009 3
            $this->match(Lexer::T_NOT);
3010 3
            $not = true;
3011
        }
3012
3013 8
        $this->match(Lexer::T_BETWEEN);
3014 8
        $arithExpr2 = $this->ArithmeticExpression();
3015 8
        $this->match(Lexer::T_AND);
3016 8
        $arithExpr3 = $this->ArithmeticExpression();
3017
3018 8
        $betweenExpr      = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3019 8
        $betweenExpr->not = $not;
3020
3021 8
        return $betweenExpr;
3022
    }
3023
3024
    /**
3025
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3026
     *
3027
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3028
     */
3029 301
    public function ComparisonExpression()
3030
    {
3031 301
        $this->lexer->glimpse();
3032
3033 301
        $leftExpr  = $this->ArithmeticExpression();
3034 301
        $operator  = $this->ComparisonOperator();
3035 301
        $rightExpr = ($this->isNextAllAnySome())
3036 3
            ? $this->QuantifiedExpression()
3037 301
            : $this->ArithmeticExpression();
3038
3039 299
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3040
    }
3041
3042
    /**
3043
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3044
     *
3045
     * @return \Doctrine\ORM\Query\AST\InExpression
3046
     */
3047 35
    public function InExpression()
3048
    {
3049 35
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3050
3051 35
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3052 6
            $this->match(Lexer::T_NOT);
3053 6
            $inExpression->not = true;
3054
        }
3055
3056 35
        $this->match(Lexer::T_IN);
3057 35
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3058
3059 35
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3060 9
            $inExpression->subselect = $this->Subselect();
3061
        } else {
3062 26
            $literals   = [];
3063 26
            $literals[] = $this->InParameter();
3064
3065 26
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3066 16
                $this->match(Lexer::T_COMMA);
3067 16
                $literals[] = $this->InParameter();
3068
            }
3069
3070 26
            $inExpression->literals = $literals;
3071
        }
3072
3073 34
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3074
3075 34
        return $inExpression;
3076
    }
3077
3078
    /**
3079
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3080
     *
3081
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3082
     */
3083 17
    public function InstanceOfExpression()
3084
    {
3085 17
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3086
3087 17
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3088 1
            $this->match(Lexer::T_NOT);
3089 1
            $instanceOfExpression->not = true;
3090
        }
3091
3092 17
        $this->match(Lexer::T_INSTANCE);
3093 17
        $this->match(Lexer::T_OF);
3094
3095 17
        $exprValues = [];
3096
3097 17
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3098 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3099
3100 2
            $exprValues[] = $this->InstanceOfParameter();
3101
3102 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3103 2
                $this->match(Lexer::T_COMMA);
3104
3105 2
                $exprValues[] = $this->InstanceOfParameter();
3106
            }
3107
3108 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3109
3110 2
            $instanceOfExpression->value = $exprValues;
3111
3112 2
            return $instanceOfExpression;
3113
        }
3114
3115 15
        $exprValues[] = $this->InstanceOfParameter();
3116
3117 15
        $instanceOfExpression->value = $exprValues;
3118
3119 15
        return $instanceOfExpression;
3120
    }
3121
3122
    /**
3123
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3124
     *
3125
     * @return mixed
3126
     */
3127 17
    public function InstanceOfParameter()
3128
    {
3129 17
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3130 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3131
3132 6
            return new AST\InputParameter($this->lexer->token['value']);
3133
        }
3134
3135 11
        $abstractSchemaName = $this->AbstractSchemaName();
3136
3137 11
        $this->validateAbstractSchemaName($abstractSchemaName);
3138
3139 11
        return $abstractSchemaName;
3140
    }
3141
3142
    /**
3143
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3144
     *
3145
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3146
     */
3147 14
    public function LikeExpression()
3148
    {
3149 14
        $stringExpr = $this->StringExpression();
3150 14
        $not        = false;
3151
3152 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3153 3
            $this->match(Lexer::T_NOT);
3154 3
            $not = true;
3155
        }
3156
3157 14
        $this->match(Lexer::T_LIKE);
3158
3159 14
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3160 3
            $this->match(Lexer::T_INPUT_PARAMETER);
3161 3
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3162
        } else {
3163 12
            $stringPattern = $this->StringPrimary();
3164
        }
3165
3166 14
        $escapeChar = null;
3167
3168 14
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3169 2
            $this->match(Lexer::T_ESCAPE);
3170 2
            $this->match(Lexer::T_STRING);
3171
3172 2
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3173
        }
3174
3175 14
        $likeExpr      = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
3176 14
        $likeExpr->not = $not;
3177
3178 14
        return $likeExpr;
3179
    }
3180
3181
    /**
3182
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3183
     *
3184
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3185
     */
3186 13
    public function NullComparisonExpression()
3187
    {
3188
        switch (true) {
3189 13
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3190
                $this->match(Lexer::T_INPUT_PARAMETER);
3191
3192
                $expr = new AST\InputParameter($this->lexer->token['value']);
3193
                break;
3194
3195 13
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3196 1
                $expr = $this->NullIfExpression();
3197 1
                break;
3198
3199 13
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3200 1
                $expr = $this->CoalesceExpression();
3201 1
                break;
3202
3203 13
            case $this->isFunction():
3204 2
                $expr = $this->FunctionDeclaration();
3205 2
                break;
3206
3207
            default:
3208
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3209 12
                $glimpse = $this->lexer->glimpse();
3210
3211 12
                if ($glimpse['type'] === Lexer::T_DOT) {
3212 8
                    $expr = $this->SingleValuedPathExpression();
3213
3214
                    // Leave switch statement
3215 8
                    break;
3216
                }
3217
3218 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3219
3220
                // Validate existing component
3221 4
                if (! isset($this->queryComponents[$lookaheadValue])) {
3222
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3223
                }
3224
3225
                // Validate SingleValuedPathExpression (ie.: "product")
3226 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3227 1
                    $expr = $this->SingleValuedPathExpression();
3228 1
                    break;
3229
                }
3230
3231
                // Validating ResultVariable
3232 3
                if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3233
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3234
                }
3235
3236 3
                $expr = $this->ResultVariable();
3237 3
                break;
3238
        }
3239
3240 13
        $nullCompExpr = new AST\NullComparisonExpression($expr);
3241
3242 13
        $this->match(Lexer::T_IS);
3243
3244 13
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3245 5
            $this->match(Lexer::T_NOT);
3246
3247 5
            $nullCompExpr->not = true;
3248
        }
3249
3250 13
        $this->match(Lexer::T_NULL);
3251
3252 13
        return $nullCompExpr;
3253
    }
3254
3255
    /**
3256
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3257
     *
3258
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
3259
     */
3260 7
    public function ExistsExpression()
3261
    {
3262 7
        $not = false;
3263
3264 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3265
            $this->match(Lexer::T_NOT);
3266
            $not = true;
3267
        }
3268
3269 7
        $this->match(Lexer::T_EXISTS);
3270 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3271
3272 7
        $existsExpression      = new AST\ExistsExpression($this->Subselect());
3273 7
        $existsExpression->not = $not;
3274
3275 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3276
3277 7
        return $existsExpression;
3278
    }
3279
3280
    /**
3281
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3282
     *
3283
     * @return string
3284
     */
3285 301
    public function ComparisonOperator()
3286
    {
3287 301
        switch ($this->lexer->lookahead['value']) {
3288
            case '=':
3289 250
                $this->match(Lexer::T_EQUALS);
3290
3291 250
                return '=';
3292
3293
            case '<':
3294 17
                $this->match(Lexer::T_LOWER_THAN);
3295 17
                $operator = '<';
3296
3297 17
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3298 5
                    $this->match(Lexer::T_EQUALS);
3299 5
                    $operator .= '=';
3300 12
                } elseif ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3301 3
                    $this->match(Lexer::T_GREATER_THAN);
3302 3
                    $operator .= '>';
3303
                }
3304
3305 17
                return $operator;
3306
3307
            case '>':
3308 47
                $this->match(Lexer::T_GREATER_THAN);
3309 47
                $operator = '>';
3310
3311 47
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3312 6
                    $this->match(Lexer::T_EQUALS);
3313 6
                    $operator .= '=';
3314
                }
3315
3316 47
                return $operator;
3317
3318
            case '!':
3319 6
                $this->match(Lexer::T_NEGATE);
3320 6
                $this->match(Lexer::T_EQUALS);
3321
3322 6
                return '<>';
3323
3324
            default:
3325
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
3326
        }
3327
    }
3328
3329
    /**
3330
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3331
     *
3332
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3333
     */
3334 156
    public function FunctionDeclaration()
3335
    {
3336 156
        $token    = $this->lexer->lookahead;
3337 156
        $funcName = strtolower($token['value']);
3338
3339 156
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3340
3341
        // Check for custom functions functions first!
3342 156
        switch (true) {
3343
            case $customFunctionDeclaration !== null:
3344 4
                return $customFunctionDeclaration;
3345
3346 152
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3347 33
                return $this->FunctionsReturningStrings();
3348
3349 124
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3350 109
                return $this->FunctionsReturningNumerics();
3351
3352 16
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3353 16
                return $this->FunctionsReturningDatetime();
3354
3355
            default:
3356
                $this->syntaxError('known function', $token);
3357
        }
3358
    }
3359
3360
    /**
3361
     * Helper function for FunctionDeclaration grammar rule.
3362
     *
3363
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3364
     */
3365 156
    private function CustomFunctionDeclaration()
3366
    {
3367 156
        $token    = $this->lexer->lookahead;
3368 156
        $funcName = strtolower($token['value']);
3369
3370
        // Check for custom functions afterwards
3371 156
        $config = $this->em->getConfiguration();
3372
3373 156
        switch (true) {
3374 156
            case ($config->getCustomStringFunction($funcName) !== null):
3375 3
                return $this->CustomFunctionsReturningStrings();
3376
3377 154
            case ($config->getCustomNumericFunction($funcName) !== null):
3378 2
                return $this->CustomFunctionsReturningNumerics();
3379
3380 152
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3381
                return $this->CustomFunctionsReturningDatetime();
3382
3383
            default:
3384 152
                return null;
3385
        }
3386
    }
3387
3388
    /**
3389
     * FunctionsReturningNumerics ::=
3390
     *      "LENGTH" "(" StringPrimary ")" |
3391
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3392
     *      "ABS" "(" SimpleArithmeticExpression ")" |
3393
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
3394
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3395
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
3396
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3397
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3398
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3399
     *
3400
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3401
     */
3402 109
    public function FunctionsReturningNumerics()
3403
    {
3404 109
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3405 109
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3406
3407 109
        $function = new $funcClass($funcNameLower);
3408 109
        $function->parse($this);
3409
3410 109
        return $function;
3411
    }
3412
3413
    /**
3414
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3415
     */
3416 2
    public function CustomFunctionsReturningNumerics()
3417
    {
3418
        // getCustomNumericFunction is case-insensitive
3419 2
        $functionName  = strtolower($this->lexer->lookahead['value']);
3420 2
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3421
3422 2
        $function = is_string($functionClass)
3423 1
            ? new $functionClass($functionName)
3424 2
            : call_user_func($functionClass, $functionName);
3425
3426 2
        $function->parse($this);
3427
3428 2
        return $function;
3429
    }
3430
3431
    /**
3432
     * FunctionsReturningDateTime ::=
3433
     *     "CURRENT_DATE" |
3434
     *     "CURRENT_TIME" |
3435
     *     "CURRENT_TIMESTAMP" |
3436
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3437
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3438
     *
3439
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3440
     */
3441 16
    public function FunctionsReturningDatetime()
3442
    {
3443 16
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3444 16
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3445
3446 16
        $function = new $funcClass($funcNameLower);
3447 16
        $function->parse($this);
3448
3449 16
        return $function;
3450
    }
3451
3452
    /**
3453
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3454
     */
3455
    public function CustomFunctionsReturningDatetime()
3456
    {
3457
        // getCustomDatetimeFunction is case-insensitive
3458
        $functionName  = $this->lexer->lookahead['value'];
3459
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3460
3461
        $function = is_string($functionClass)
3462
            ? new $functionClass($functionName)
3463
            : call_user_func($functionClass, $functionName);
3464
3465
        $function->parse($this);
3466
3467
        return $function;
3468
    }
3469
3470
    /**
3471
     * FunctionsReturningStrings ::=
3472
     *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3473
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3474
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3475
     *   "LOWER" "(" StringPrimary ")" |
3476
     *   "UPPER" "(" StringPrimary ")" |
3477
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3478
     *
3479
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3480
     */
3481 33
    public function FunctionsReturningStrings()
3482
    {
3483 33
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3484 33
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3485
3486 33
        $function = new $funcClass($funcNameLower);
3487 33
        $function->parse($this);
3488
3489 33
        return $function;
3490
    }
3491
3492
    /**
3493
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3494
     */
3495 3
    public function CustomFunctionsReturningStrings()
3496
    {
3497
        // getCustomStringFunction is case-insensitive
3498 3
        $functionName  = $this->lexer->lookahead['value'];
3499 3
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3500
3501 3
        $function = is_string($functionClass)
3502 2
            ? new $functionClass($functionName)
3503 3
            : call_user_func($functionClass, $functionName);
3504
3505 3
        $function->parse($this);
3506
3507 3
        return $function;
3508
    }
3509
}
3510