Failed Conditions
Push — master ( 2ccf23...d791f7 )
by Michael
10:40
created

Parser::RangeVariableDeclaration()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 31
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 18
nc 4
nop 0
dl 0
loc 31
ccs 15
cts 15
cp 1
crap 4
rs 8.5806
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 838
    public function __construct(Query $query)
169
    {
170 838
        $this->query        = $query;
171 838
        $this->em           = $query->getEntityManager();
172 838
        $this->lexer        = new Lexer($query->getDQL());
173 838
        $this->parserResult = new ParserResult();
174 838
    }
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 838
    public function getAST()
235
    {
236
        // Parse & build AST
237 838
        $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 803
        $this->processDeferredIdentificationVariables();
242
243 801
        if ($this->deferredPartialObjectExpressions) {
244 9
            $this->processDeferredPartialObjectExpressions();
245
        }
246
247 800
        if ($this->deferredPathExpressions) {
248 593
            $this->processDeferredPathExpressions();
249
        }
250
251 798
        if ($this->deferredResultVariables) {
252 32
            $this->processDeferredResultVariables();
253
        }
254
255 798
        if ($this->deferredNewObjectExpressions) {
256 26
            $this->processDeferredNewObjectExpressions($AST);
257
        }
258
259 794
        $this->processRootEntityAliasSelected();
260
261
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
262 793
        $this->fixIdentificationVariableOrder($AST);
263
264 793
        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 849
    public function match($token)
278
    {
279 849
        $lookaheadType = $this->lexer->lookahead['type'];
280
281
        // Short-circuit on first condition, usually types match
282 849
        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 842
        $this->lexer->moveNext();
300 842
    }
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 838
    public function parse()
328
    {
329 838
        $AST = $this->getAST();
330
331 793
        $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
332 793
        if ($customWalkers !== false) {
333 96
            $this->customTreeWalkers = $customWalkers;
334
        }
335
336 793
        $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER);
337
338 793
        if ($customOutputWalker !== false) {
339 79
            $this->customOutputWalker = $customOutputWalker;
340
        }
341
342
        // Run any custom tree walkers over the AST
343 793
        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 787
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
368 787
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
369
370
        // Assign an SQL executor to the parser result
371 787
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
372
373 779
        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 793
    private function fixIdentificationVariableOrder($AST)
386
    {
387 793
        if (count($this->identVariableExpressions) <= 1) {
388 618
            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 357
    private function isMathOperator($token)
508
    {
509 357
        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 400
    private function isFunction()
518
    {
519 400
        $lookaheadType = $this->lexer->lookahead['type'];
520 400
        $peek          = $this->lexer->peek();
521
522 400
        $this->lexer->resetPeek();
523
524 400
        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 803
    private function processDeferredIdentificationVariables()
554
    {
555 803
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
556 782
            $identVariable = $deferredItem['expression'];
557
558
            // Check if IdentificationVariable exists in queryComponents
559 782
            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 782
            $qComp = $this->queryComponents[$identVariable];
567
568
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
569 782
            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 782
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
578 1
                $this->semanticalError(
579 1
                    sprintf("'%s' is used outside the scope of its declaration.", $identVariable),
580 782
                    $deferredItem['token']
581
                );
582
            }
583
        }
584 801
    }
585
586
    /**
587
     * Validates that the given <tt>NewObjectExpression</tt>.
588
     *
589
     * @param \Doctrine\ORM\Query\AST\SelectClause $AST
590
     */
591 26
    private function processDeferredNewObjectExpressions($AST)
592
    {
593 26
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
594 26
            $expression    = $deferredItem['expression'];
595 26
            $token         = $deferredItem['token'];
596 26
            $className     = $expression->className;
597 26
            $args          = $expression->args;
598 26
            $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
599
600
            // If the namespace is not given then assumes the first FROM entity namespace
601 26
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
602 10
                $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
603 10
                $fqcn      = $namespace . '\\' . $className;
604
605 10
                if (class_exists($fqcn)) {
606 10
                    $expression->className = $fqcn;
607 10
                    $className             = $fqcn;
608
                }
609
            }
610
611 26
            if (! class_exists($className)) {
612 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
613
            }
614
615 25
            $class = new \ReflectionClass($className);
616
617 25
            if (! $class->isInstantiable()) {
618 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
619
            }
620
621 24
            if ($class->getConstructor() === null) {
622 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
623
            }
624
625 23
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
626 23
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
627
            }
628
        }
629 22
    }
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 593
    private function processDeferredPathExpressions()
711
    {
712 593
        foreach ($this->deferredPathExpressions as $deferredItem) {
713 593
            $pathExpression = $deferredItem['expression'];
714
715 593
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
716 593
            $class = $qComp['metadata'];
717 593
            $field = $pathExpression->field;
718
719 593
            if ($field === null) {
720 40
                $field = $pathExpression->field = $class->identifier[0];
721
            }
722
723 593
            $property = $class->getProperty($field);
724
725
            // Check if field or association exists
726 593
            if (! $property) {
727
                $this->semanticalError(
728
                    'Class ' . $class->getClassName() . ' has no field or association named ' . $field,
729
                    $deferredItem['token']
730
                );
731
            }
732
733 593
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
734
735 593
            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 593
            $expectedType = $pathExpression->expectedType;
744
745 593
            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 591
            $pathExpression->type = $fieldType;
775
        }
776 591
    }
777
778 794
    private function processRootEntityAliasSelected()
779
    {
780 794
        if (! $this->identVariableExpressions) {
781 233
            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 838
    public function QueryLanguage()
801
    {
802 838
        $statement = null;
803
804 838
        $this->lexer->moveNext();
805
806 838
        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 774
                $statement = $this->SelectStatement();
809 743
                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 806
        if ($this->lexer->lookahead !== null) {
826 3
            $this->syntaxError('end of string');
827
        }
828
829 803
        return $statement;
830
    }
831
832
    /**
833
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
834
     *
835
     * @return \Doctrine\ORM\Query\AST\SelectStatement
836
     */
837 774
    public function SelectStatement()
838
    {
839 774
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
840
841 747
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
842 744
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
843 743
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
844 743
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
845
846 743
        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 806
    public function IdentificationVariable()
883
    {
884 806
        $this->match(Lexer::T_IDENTIFIER);
885
886 806
        $identVariable = $this->lexer->token['value'];
887
888 806
        $this->deferredIdentificationVariables[] = [
889 806
            'expression'   => $identVariable,
890 806
            'nestingLevel' => $this->nestingLevel,
891 806
            'token'        => $this->lexer->token,
892
        ];
893
894 806
        return $identVariable;
895
    }
896
897
    /**
898
     * AliasIdentificationVariable = identifier
899
     *
900
     * @return string
901
     */
902 814
    public function AliasIdentificationVariable()
903
    {
904 814
        $this->match(Lexer::T_IDENTIFIER);
905
906 814
        $aliasIdentVariable = $this->lexer->token['value'];
907 814
        $exists             = isset($this->queryComponents[$aliasIdentVariable]);
908
909 814
        if ($exists) {
910 2
            $this->semanticalError(sprintf("'%s' is already defined.", $aliasIdentVariable), $this->lexer->token);
911
        }
912
913 814
        return $aliasIdentVariable;
914
    }
915
916
    /**
917
     * AbstractSchemaName ::= fully_qualified_name | identifier
918
     *
919
     * @return string
920
     */
921 826
    public function AbstractSchemaName()
922
    {
923 826
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
924 819
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
925
926 819
            return $this->lexer->token['value'];
927
        }
928
929 18
        $this->match(Lexer::T_IDENTIFIER);
930
931 17
        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 822
    private function validateAbstractSchemaName($schemaName) : void
942
    {
943 822
        if (class_exists($schemaName, true) || interface_exists($schemaName, true)) {
944 813
            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 130
    public function AliasResultVariable()
967
    {
968 130
        $this->match(Lexer::T_IDENTIFIER);
969
970 126
        $resultVariable = $this->lexer->token['value'];
971 126
        $exists         = isset($this->queryComponents[$resultVariable]);
972
973 126
        if ($exists) {
974 2
            $this->semanticalError(sprintf("'%s' is already defined.", $resultVariable), $this->lexer->token);
975
        }
976
977 126
        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 256
    public function JoinAssociationPathExpression()
1007
    {
1008 256
        $identVariable = $this->IdentificationVariable();
1009
1010 256
        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 256
        $this->match(Lexer::T_DOT);
1017 256
        $this->match(Lexer::T_IDENTIFIER);
1018
1019 256
        $field = $this->lexer->token['value'];
1020
1021
        // Validate association field
1022 256
        $qComp    = $this->queryComponents[$identVariable];
1023 256
        $class    = $qComp['metadata'];
1024 256
        $property = $class->getProperty($field);
1025
1026 256
        if (! ($property !== null && $property instanceof AssociationMetadata)) {
1027
            $this->semanticalError('Class ' . $class->getClassName() . ' has no association named ' . $field);
1028
        }
1029
1030 256
        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 603
    public function PathExpression($expectedTypes)
1044
    {
1045 603
        $identVariable = $this->IdentificationVariable();
1046 603
        $field         = null;
1047
1048 603
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1049 597
            $this->match(Lexer::T_DOT);
1050 597
            $this->match(Lexer::T_IDENTIFIER);
1051
1052 597
            $field = $this->lexer->token['value'];
1053
1054 597
            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 603
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1063
1064
        // Defer PathExpression validation if requested to be deferred
1065 603
        $this->deferredPathExpressions[] = [
1066 603
            'expression'   => $pathExpr,
1067 603
            'nestingLevel' => $this->nestingLevel,
1068 603
            'token'        => $this->lexer->token,
1069
        ];
1070
1071 603
        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 512
    public function SingleValuedPathExpression()
1093
    {
1094 512
        return $this->PathExpression(
1095 512
            AST\PathExpression::TYPE_STATE_FIELD |
1096 512
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1097
        );
1098
    }
1099
1100
    /**
1101
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1102
     *
1103
     * @return \Doctrine\ORM\Query\AST\PathExpression
1104
     */
1105 205
    public function StateFieldPathExpression()
1106
    {
1107 205
        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 774
    public function SelectClause()
1136
    {
1137 774
        $isDistinct = false;
1138 774
        $this->match(Lexer::T_SELECT);
1139
1140
        // Check for DISTINCT
1141 774
        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 774
        $selectExpressions   = [];
1149 774
        $selectExpressions[] = $this->SelectExpression();
1150
1151 766
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1152 296
            $this->match(Lexer::T_COMMA);
1153
1154 296
            $selectExpressions[] = $this->SelectExpression();
1155
        }
1156
1157 765
        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 765
    public function FromClause()
1280
    {
1281 765
        $this->match(Lexer::T_FROM);
1282
1283 760
        $identificationVariableDeclarations   = [];
1284 760
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1285
1286 747
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1287 7
            $this->match(Lexer::T_COMMA);
1288
1289 7
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1290
        }
1291
1292 747
        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 180
    public function OrderByClause()
1367
    {
1368 180
        $this->match(Lexer::T_ORDER);
1369 180
        $this->match(Lexer::T_BY);
1370
1371 180
        $orderByItems   = [];
1372 180
        $orderByItems[] = $this->OrderByItem();
1373
1374 180
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1375 15
            $this->match(Lexer::T_COMMA);
1376
1377 15
            $orderByItems[] = $this->OrderByItem();
1378
        }
1379
1380 180
        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 180
    public function OrderByItem()
1457
    {
1458 180
        $this->lexer->peek(); // lookahead => '.'
1459 180
        $this->lexer->peek(); // lookahead => token after '.'
1460
1461 180
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1462
1463 180
        $this->lexer->resetPeek();
1464
1465 180
        $glimpse = $this->lexer->glimpse();
1466
1467
        switch (true) {
1468 180
            case ($this->isFunction()):
1469 2
                $expr = $this->FunctionDeclaration();
1470 2
                break;
1471
1472 178
            case ($this->isMathOperator($peek)):
1473 25
                $expr = $this->SimpleArithmeticExpression();
1474 25
                break;
1475
1476 154
            case ($glimpse['type'] === Lexer::T_DOT):
1477 139
                $expr = $this->SingleValuedPathExpression();
1478 139
                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 180
        $type = 'ASC';
1490 180
        $item = new AST\OrderByItem($expr);
1491
1492
        switch (true) {
1493 180
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1494 95
                $this->match(Lexer::T_DESC);
1495 95
                $type = 'DESC';
1496 95
                break;
1497
1498 152
            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 180
        $item->type = $type;
1507
1508 180
        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 762
    public function IdentificationVariableDeclaration()
1547
    {
1548 762
        $joins                    = [];
1549 762
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1550 752
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1551 7
            ? $this->IndexBy()
1552 752
            : null;
1553
1554 752
        $rangeVariableDeclaration->isRoot = true;
1555
1556 752
        while ($this->lexer->isNextToken(Lexer::T_LEFT) ||
1557 752
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1558 752
            $this->lexer->isNextToken(Lexer::T_JOIN)
1559
        ) {
1560 278
            $joins[] = $this->Join();
1561
        }
1562
1563 749
        return new AST\IdentificationVariableDeclaration(
1564 749
            $rangeVariableDeclaration,
1565 749
            $indexBy,
1566 749
            $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 278
    public function Join()
1638
    {
1639
        // Check Join type
1640 278
        $joinType = AST\Join::JOIN_TYPE_INNER;
1641
1642
        switch (true) {
1643 278
            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 213
            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 278
        $this->match(Lexer::T_JOIN);
1665
1666 278
        $next            = $this->lexer->glimpse();
1667 278
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1668 275
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1669 275
        $join            = new AST\Join($joinType, $joinDeclaration);
1670
1671
        // Describe non-root join declaration
1672 275
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1673 22
            $joinDeclaration->isRoot = false;
1674
        }
1675
1676
        // Check for ad-hoc Join conditions
1677 275
        if ($adhocConditions) {
1678 25
            $this->match(Lexer::T_WITH);
1679
1680 25
            $join->conditionalExpression = $this->ConditionalExpression();
1681
        }
1682
1683 275
        return $join;
1684
    }
1685
1686
    /**
1687
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1688
     *
1689
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1690
     *
1691
     * @throws QueryException
1692
     */
1693 762
    public function RangeVariableDeclaration()
1694
    {
1695 762
        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 761
        $abstractSchemaName = $this->AbstractSchemaName();
1700
1701 760
        $this->validateAbstractSchemaName($abstractSchemaName);
1702
1703 752
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1704 6
            $this->match(Lexer::T_AS);
1705
        }
1706
1707 752
        $token                       = $this->lexer->lookahead;
1708 752
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1709 752
        $classMetadata               = $this->em->getClassMetadata($abstractSchemaName);
1710
1711
        // Building queryComponent
1712
        $queryComponent = [
1713 752
            'metadata'     => $classMetadata,
1714
            'parent'       => null,
1715
            'relation'     => null,
1716
            'map'          => null,
1717 752
            'nestingLevel' => $this->nestingLevel,
1718 752
            'token'        => $token,
1719
        ];
1720
1721 752
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1722
1723 752
        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 256
    public function JoinAssociationDeclaration()
1732
    {
1733 256
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1734
1735 256
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1736 5
            $this->match(Lexer::T_AS);
1737
        }
1738
1739 256
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1740 254
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1741
1742 254
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1743 254
        $field                  = $joinAssociationPathExpression->associationField;
1744
1745 254
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1746 254
        $association = $class->getProperty($field);
1747 254
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
1748
1749
        // Building queryComponent
1750
        $joinQueryComponent = [
1751 254
            'metadata'     => $targetClass,
1752 254
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1753 254
            'relation'     => $association,
1754
            'map'          => null,
1755 254
            'nestingLevel' => $this->nestingLevel,
1756 254
            'token'        => $this->lexer->lookahead,
1757
        ];
1758
1759 254
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1760
1761 254
        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 26
    public function NewObjectExpression()
1828
    {
1829 26
        $this->match(Lexer::T_NEW);
1830
1831 26
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1832 26
        $token     = $this->lexer->token;
1833
1834 26
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1835
1836 26
        $args[] = $this->NewObjectArg();
1837
1838 26
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1839 22
            $this->match(Lexer::T_COMMA);
1840
1841 22
            $args[] = $this->NewObjectArg();
1842
        }
1843
1844 26
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1845
1846 26
        $expression = new AST\NewObjectExpression($className, $args);
1847
1848
        // Defer NewObjectExpression validation
1849 26
        $this->deferredNewObjectExpressions[] = [
1850 26
            'token'        => $token,
1851 26
            'expression'   => $expression,
1852 26
            'nestingLevel' => $this->nestingLevel,
1853
        ];
1854
1855 26
        return $expression;
1856
    }
1857
1858
    /**
1859
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1860
     *
1861
     * @return mixed
1862
     */
1863 26
    public function NewObjectArg()
1864
    {
1865 26
        $token = $this->lexer->lookahead;
1866 26
        $peek  = $this->lexer->glimpse();
1867
1868 26
        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 26
        return $this->ScalarExpression();
1877
    }
1878
1879
    /**
1880
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1881
     *
1882
     * @return \Doctrine\ORM\Query\AST\IndexBy
1883
     */
1884 11
    public function IndexBy()
1885
    {
1886 11
        $this->match(Lexer::T_INDEX);
1887 11
        $this->match(Lexer::T_BY);
1888 11
        $pathExpr = $this->StateFieldPathExpression();
1889
1890
        // Add the INDEX BY info to the query component
1891 11
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1892
1893 11
        return new AST\IndexBy($pathExpr);
1894
    }
1895
1896
    /**
1897
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1898
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1899
     *                      InstanceOfExpression
1900
     *
1901
     * @return mixed One of the possible expressions or subexpressions.
1902
     */
1903 163
    public function ScalarExpression()
1904
    {
1905 163
        $lookahead = $this->lexer->lookahead['type'];
1906 163
        $peek      = $this->lexer->glimpse();
1907
1908 163
        switch (true) {
1909
            case ($lookahead === Lexer::T_INTEGER):
1910 160
            case ($lookahead === Lexer::T_FLOAT):
1911
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1912 160
            case ($lookahead === Lexer::T_MINUS):
1913 160
            case ($lookahead === Lexer::T_PLUS):
1914 17
                return $this->SimpleArithmeticExpression();
1915
1916 160
            case ($lookahead === Lexer::T_STRING):
1917 13
                return $this->StringPrimary();
1918
1919 158
            case ($lookahead === Lexer::T_TRUE):
1920 158
            case ($lookahead === Lexer::T_FALSE):
1921 3
                $this->match($lookahead);
1922
1923 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1924
1925 158
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
1926
                switch (true) {
1927 1
                    case $this->isMathOperator($peek):
1928
                        // :param + u.value
1929 1
                        return $this->SimpleArithmeticExpression();
1930
                    default:
1931
                        return $this->InputParameter();
1932
                }
1933
                // cannot get here
1934
1935 158
            case ($lookahead === Lexer::T_CASE):
1936 154
            case ($lookahead === Lexer::T_COALESCE):
1937 154
            case ($lookahead === Lexer::T_NULLIF):
1938
                // Since NULLIF and COALESCE can be identified as a function,
1939
                // we need to check these before checking for FunctionDeclaration
1940 8
                return $this->CaseExpression();
1941
1942 154
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1943 4
                return $this->SimpleArithmeticExpression();
1944
1945
            // this check must be done before checking for a filed path expression
1946 151
            case ($this->isFunction()):
1947 27
                $this->lexer->peek(); // "("
1948
1949
                switch (true) {
1950 27
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1951
                        // SUM(u.id) + COUNT(u.id)
1952 7
                        return $this->SimpleArithmeticExpression();
1953
1954
                    default:
1955
                        // IDENTITY(u)
1956 22
                        return $this->FunctionDeclaration();
1957
                }
1958
1959
                break;
1960
            // it is no function, so it must be a field path
1961 132
            case ($lookahead === Lexer::T_IDENTIFIER):
1962 132
                $this->lexer->peek(); // lookahead => '.'
1963 132
                $this->lexer->peek(); // lookahead => token after '.'
1964 132
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1965 132
                $this->lexer->resetPeek();
1966
1967 132
                if ($this->isMathOperator($peek)) {
1968 7
                    return $this->SimpleArithmeticExpression();
1969
                }
1970
1971 127
                return $this->StateFieldPathExpression();
1972
1973
            default:
1974
                $this->syntaxError();
1975
        }
1976
    }
1977
1978
    /**
1979
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
1980
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1981
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1982
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1983
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1984
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1985
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1986
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1987
     *
1988
     * @return mixed One of the possible expressions or subexpressions.
1989
     */
1990 19
    public function CaseExpression()
1991
    {
1992 19
        $lookahead = $this->lexer->lookahead['type'];
1993
1994 19
        switch ($lookahead) {
1995
            case Lexer::T_NULLIF:
1996 5
                return $this->NullIfExpression();
1997
1998
            case Lexer::T_COALESCE:
1999 2
                return $this->CoalesceExpression();
2000
2001
            case Lexer::T_CASE:
2002 14
                $this->lexer->resetPeek();
2003 14
                $peek = $this->lexer->peek();
2004
2005 14
                if ($peek['type'] === Lexer::T_WHEN) {
2006 9
                    return $this->GeneralCaseExpression();
2007
                }
2008
2009 5
                return $this->SimpleCaseExpression();
2010
2011
            default:
2012
                // Do nothing
2013
                break;
2014
        }
2015
2016
        $this->syntaxError();
2017
    }
2018
2019
    /**
2020
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2021
     *
2022
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2023
     */
2024 3
    public function CoalesceExpression()
2025
    {
2026 3
        $this->match(Lexer::T_COALESCE);
2027 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2028
2029
        // Process ScalarExpressions (1..N)
2030 3
        $scalarExpressions   = [];
2031 3
        $scalarExpressions[] = $this->ScalarExpression();
2032
2033 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2034 3
            $this->match(Lexer::T_COMMA);
2035
2036 3
            $scalarExpressions[] = $this->ScalarExpression();
2037
        }
2038
2039 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2040
2041 3
        return new AST\CoalesceExpression($scalarExpressions);
2042
    }
2043
2044
    /**
2045
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2046
     *
2047
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2048
     */
2049 5
    public function NullIfExpression()
2050
    {
2051 5
        $this->match(Lexer::T_NULLIF);
2052 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2053
2054 5
        $firstExpression = $this->ScalarExpression();
2055 5
        $this->match(Lexer::T_COMMA);
2056 5
        $secondExpression = $this->ScalarExpression();
2057
2058 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2059
2060 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2061
    }
2062
2063
    /**
2064
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2065
     *
2066
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2067
     */
2068 9
    public function GeneralCaseExpression()
2069
    {
2070 9
        $this->match(Lexer::T_CASE);
2071
2072
        // Process WhenClause (1..N)
2073 9
        $whenClauses = [];
2074
2075
        do {
2076 9
            $whenClauses[] = $this->WhenClause();
2077 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2078
2079 9
        $this->match(Lexer::T_ELSE);
2080 9
        $scalarExpression = $this->ScalarExpression();
2081 9
        $this->match(Lexer::T_END);
2082
2083 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2084
    }
2085
2086
    /**
2087
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2088
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2089
     *
2090
     * @return AST\SimpleCaseExpression
2091
     */
2092 5
    public function SimpleCaseExpression()
2093
    {
2094 5
        $this->match(Lexer::T_CASE);
2095 5
        $caseOperand = $this->StateFieldPathExpression();
2096
2097
        // Process SimpleWhenClause (1..N)
2098 5
        $simpleWhenClauses = [];
2099
2100
        do {
2101 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2102 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2103
2104 5
        $this->match(Lexer::T_ELSE);
2105 5
        $scalarExpression = $this->ScalarExpression();
2106 5
        $this->match(Lexer::T_END);
2107
2108 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2109
    }
2110
2111
    /**
2112
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2113
     *
2114
     * @return \Doctrine\ORM\Query\AST\WhenClause
2115
     */
2116 9
    public function WhenClause()
2117
    {
2118 9
        $this->match(Lexer::T_WHEN);
2119 9
        $conditionalExpression = $this->ConditionalExpression();
2120 9
        $this->match(Lexer::T_THEN);
2121
2122 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2123
    }
2124
2125
    /**
2126
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2127
     *
2128
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2129
     */
2130 5
    public function SimpleWhenClause()
2131
    {
2132 5
        $this->match(Lexer::T_WHEN);
2133 5
        $conditionalExpression = $this->ScalarExpression();
2134 5
        $this->match(Lexer::T_THEN);
2135
2136 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2137
    }
2138
2139
    /**
2140
     * SelectExpression ::= (
2141
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2142
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2143
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2144
     *
2145
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2146
     */
2147 774
    public function SelectExpression()
2148
    {
2149 774
        $expression    = null;
2150 774
        $identVariable = null;
2151 774
        $peek          = $this->lexer->glimpse();
2152 774
        $lookaheadType = $this->lexer->lookahead['type'];
2153
2154 774
        switch (true) {
2155
            // ScalarExpression (u.name)
2156 698
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2157 106
                $expression = $this->ScalarExpression();
2158 106
                break;
2159
2160
            // IdentificationVariable (u)
2161 714
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2162 589
                $expression = $identVariable = $this->IdentificationVariable();
2163 589
                break;
2164
2165
            // 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...
2166 187
            case ($lookaheadType === Lexer::T_CASE):
2167 182
            case ($lookaheadType === Lexer::T_COALESCE):
2168 180
            case ($lookaheadType === Lexer::T_NULLIF):
2169 9
                $expression = $this->CaseExpression();
2170 9
                break;
2171
2172
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2173 178
            case ($this->isFunction()):
2174 102
                $this->lexer->peek(); // "("
2175
2176
                switch (true) {
2177 102
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2178
                        // SUM(u.id) + COUNT(u.id)
2179 2
                        $expression = $this->ScalarExpression();
2180 2
                        break;
2181
2182
                    default:
2183
                        // IDENTITY(u)
2184 100
                        $expression = $this->FunctionDeclaration();
2185 100
                        break;
2186
                }
2187
2188 102
                break;
2189
2190
            // PartialObjectExpression (PARTIAL u.{id, name})
2191 77
            case ($lookaheadType === Lexer::T_PARTIAL):
2192 9
                $expression    = $this->PartialObjectExpression();
2193 9
                $identVariable = $expression->identificationVariable;
2194 9
                break;
2195
2196
            // Subselect
2197 68
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2198 23
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2199 23
                $expression = $this->Subselect();
2200 23
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2201 23
                break;
2202
2203
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2204 45
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2205 41
            case ($lookaheadType === Lexer::T_INTEGER):
2206 39
            case ($lookaheadType === Lexer::T_STRING):
2207 30
            case ($lookaheadType === Lexer::T_FLOAT):
2208
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2209 30
            case ($lookaheadType === Lexer::T_MINUS):
2210 30
            case ($lookaheadType === Lexer::T_PLUS):
2211 16
                $expression = $this->SimpleArithmeticExpression();
2212 16
                break;
2213
2214
            // 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...
2215 29
            case ($lookaheadType === Lexer::T_NEW):
2216 26
                $expression = $this->NewObjectExpression();
2217 26
                break;
2218
2219
            default:
2220 3
                $this->syntaxError(
2221 3
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2222 3
                    $this->lexer->lookahead
2223
                );
2224
        }
2225
2226
        // [["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...
2227 771
        $mustHaveAliasResultVariable = false;
2228
2229 771
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2230 121
            $this->match(Lexer::T_AS);
2231
2232 121
            $mustHaveAliasResultVariable = true;
2233
        }
2234
2235 771
        $hiddenAliasResultVariable = false;
2236
2237 771
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2238 10
            $this->match(Lexer::T_HIDDEN);
2239
2240 10
            $hiddenAliasResultVariable = true;
2241
        }
2242
2243 771
        $aliasResultVariable = null;
2244
2245 771
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2246 130
            $token               = $this->lexer->lookahead;
2247 130
            $aliasResultVariable = $this->AliasResultVariable();
2248
2249
            // Include AliasResultVariable in query components.
2250 125
            $this->queryComponents[$aliasResultVariable] = [
2251 125
                'resultVariable' => $expression,
2252 125
                'nestingLevel'   => $this->nestingLevel,
2253 125
                'token'          => $token,
2254
            ];
2255
        }
2256
2257
        // AST
2258
2259 766
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2260
2261 766
        if ($identVariable) {
2262 596
            $this->identVariableExpressions[$identVariable] = $expr;
2263
        }
2264
2265 766
        return $expr;
2266
    }
2267
2268
    /**
2269
     * SimpleSelectExpression ::= (
2270
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2271
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2272
     * ) [["AS"] AliasResultVariable]
2273
     *
2274
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2275
     */
2276 49
    public function SimpleSelectExpression()
2277
    {
2278 49
        $peek = $this->lexer->glimpse();
2279
2280 49
        switch ($this->lexer->lookahead['type']) {
2281
            case Lexer::T_IDENTIFIER:
2282 19
                switch (true) {
2283 19
                    case ($peek['type'] === Lexer::T_DOT):
2284 16
                        $expression = $this->StateFieldPathExpression();
2285
2286 16
                        return new AST\SimpleSelectExpression($expression);
2287
2288 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2289 2
                        $expression = $this->IdentificationVariable();
2290
2291 2
                        return new AST\SimpleSelectExpression($expression);
2292
2293 1
                    case ($this->isFunction()):
2294
                        // SUM(u.id) + COUNT(u.id)
2295 1
                        if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
2296
                            return new AST\SimpleSelectExpression($this->ScalarExpression());
2297
                        }
2298
                        // COUNT(u.id)
2299 1
                        if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
2300
                            return new AST\SimpleSelectExpression($this->AggregateExpression());
2301
                        }
2302
                        // IDENTITY(u)
2303 1
                        return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2304
2305
                    default:
2306
                        // Do nothing
2307
                }
2308
                break;
2309
2310
            case Lexer::T_OPEN_PARENTHESIS:
2311 3
                if ($peek['type'] !== Lexer::T_SELECT) {
2312
                    // Shortcut: ScalarExpression => SimpleArithmeticExpression
2313 3
                    $expression = $this->SimpleArithmeticExpression();
2314
2315 3
                    return new AST\SimpleSelectExpression($expression);
2316
                }
2317
2318
                // Subselect
2319
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2320
                $expression = $this->Subselect();
2321
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2322
2323
                return new AST\SimpleSelectExpression($expression);
2324
2325
            default:
2326
                // Do nothing
2327
        }
2328
2329 28
        $this->lexer->peek();
2330
2331 28
        $expression = $this->ScalarExpression();
2332 28
        $expr       = new AST\SimpleSelectExpression($expression);
2333
2334 28
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2335 1
            $this->match(Lexer::T_AS);
2336
        }
2337
2338 28
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2339 2
            $token                             = $this->lexer->lookahead;
2340 2
            $resultVariable                    = $this->AliasResultVariable();
2341 2
            $expr->fieldIdentificationVariable = $resultVariable;
2342
2343
            // Include AliasResultVariable in query components.
2344 2
            $this->queryComponents[$resultVariable] = [
2345 2
                'resultvariable' => $expr,
2346 2
                'nestingLevel'   => $this->nestingLevel,
2347 2
                'token'          => $token,
2348
            ];
2349
        }
2350
2351 28
        return $expr;
2352
    }
2353
2354
    /**
2355
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2356
     *
2357
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2358
     */
2359 385
    public function ConditionalExpression()
2360
    {
2361 385
        $conditionalTerms   = [];
2362 385
        $conditionalTerms[] = $this->ConditionalTerm();
2363
2364 382
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2365 16
            $this->match(Lexer::T_OR);
2366
2367 16
            $conditionalTerms[] = $this->ConditionalTerm();
2368
        }
2369
2370
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2371
        // if only one AST\ConditionalTerm is defined
2372 382
        if (\count($conditionalTerms) === 1) {
2373 374
            return $conditionalTerms[0];
2374
        }
2375
2376 16
        return new AST\ConditionalExpression($conditionalTerms);
2377
    }
2378
2379
    /**
2380
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2381
     *
2382
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2383
     */
2384 385
    public function ConditionalTerm()
2385
    {
2386 385
        $conditionalFactors   = [];
2387 385
        $conditionalFactors[] = $this->ConditionalFactor();
2388
2389 382
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2390 32
            $this->match(Lexer::T_AND);
2391
2392 32
            $conditionalFactors[] = $this->ConditionalFactor();
2393
        }
2394
2395
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2396
        // if only one AST\ConditionalFactor is defined
2397 382
        if (\count($conditionalFactors) === 1) {
2398 363
            return $conditionalFactors[0];
2399
        }
2400
2401 32
        return new AST\ConditionalTerm($conditionalFactors);
2402
    }
2403
2404
    /**
2405
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2406
     *
2407
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2408
     */
2409 385
    public function ConditionalFactor()
2410
    {
2411 385
        $not = false;
2412
2413 385
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2414 6
            $this->match(Lexer::T_NOT);
2415
2416 6
            $not = true;
2417
        }
2418
2419 385
        $conditionalPrimary = $this->ConditionalPrimary();
2420
2421
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2422
        // if only one AST\ConditionalPrimary is defined
2423 382
        if (! $not) {
2424 380
            return $conditionalPrimary;
2425
        }
2426
2427 6
        $conditionalFactor      = new AST\ConditionalFactor($conditionalPrimary);
2428 6
        $conditionalFactor->not = $not;
2429
2430 6
        return $conditionalFactor;
2431
    }
2432
2433
    /**
2434
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2435
     *
2436
     * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
2437
     */
2438 385
    public function ConditionalPrimary()
2439
    {
2440 385
        $condPrimary = new AST\ConditionalPrimary;
2441
2442 385
        if (! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2443 376
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2444
2445 373
            return $condPrimary;
2446
        }
2447
2448
        // Peek beyond the matching closing parenthesis ')'
2449 25
        $peek = $this->peekBeyondClosingParenthesis();
2450
2451 25
        if (in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!='], true) ||
2452 22
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS], true) ||
2453 25
            $this->isMathOperator($peek)) {
2454 15
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2455
2456 15
            return $condPrimary;
2457
        }
2458
2459 21
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2460 21
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2461 21
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2462
2463 21
        return $condPrimary;
2464
    }
2465
2466
    /**
2467
     * SimpleConditionalExpression ::=
2468
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2469
     *      InExpression | NullComparisonExpression | ExistsExpression |
2470
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2471
     *      InstanceOfExpression
2472
     */
2473 385
    public function SimpleConditionalExpression()
2474
    {
2475 385
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2476 7
            return $this->ExistsExpression();
2477
        }
2478
2479 385
        $token     = $this->lexer->lookahead;
2480 385
        $peek      = $this->lexer->glimpse();
2481 385
        $lookahead = $token;
2482
2483 385
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2484
            $token = $this->lexer->glimpse();
2485
        }
2486
2487 385
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2488
            // Peek beyond the matching closing parenthesis.
2489 361
            $beyond = $this->lexer->peek();
2490
2491 361
            switch ($peek['value']) {
2492
                case '(':
2493
                    // Peeks beyond the matched closing parenthesis.
2494 37
                    $token = $this->peekBeyondClosingParenthesis(false);
2495
2496 37
                    if ($token['type'] === Lexer::T_NOT) {
2497 3
                        $token = $this->lexer->peek();
2498
                    }
2499
2500 37
                    if ($token['type'] === Lexer::T_IS) {
2501 2
                        $lookahead = $this->lexer->peek();
2502
                    }
2503 37
                    break;
2504
2505
                default:
2506
                    // Peek beyond the PathExpression or InputParameter.
2507 333
                    $token = $beyond;
2508
2509 333
                    while ($token['value'] === '.') {
2510 288
                        $this->lexer->peek();
2511
2512 288
                        $token = $this->lexer->peek();
2513
                    }
2514
2515
                    // Also peek beyond a NOT if there is one.
2516 333
                    if ($token['type'] === Lexer::T_NOT) {
2517 11
                        $token = $this->lexer->peek();
2518
                    }
2519
2520
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2521 333
                    $lookahead = $this->lexer->peek();
2522
            }
2523
2524
            // Also peek beyond a NOT if there is one.
2525 361
            if ($lookahead['type'] === Lexer::T_NOT) {
2526 7
                $lookahead = $this->lexer->peek();
2527
            }
2528
2529 361
            $this->lexer->resetPeek();
2530
        }
2531
2532 385
        if ($token['type'] === Lexer::T_BETWEEN) {
2533 8
            return $this->BetweenExpression();
2534
        }
2535
2536 379
        if ($token['type'] === Lexer::T_LIKE) {
2537 14
            return $this->LikeExpression();
2538
        }
2539
2540 366
        if ($token['type'] === Lexer::T_IN) {
2541 35
            return $this->InExpression();
2542
        }
2543
2544 340
        if ($token['type'] === Lexer::T_INSTANCE) {
2545 17
            return $this->InstanceOfExpression();
2546
        }
2547
2548 323
        if ($token['type'] === Lexer::T_MEMBER) {
2549 8
            return $this->CollectionMemberExpression();
2550
        }
2551
2552 315
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2553 13
            return $this->NullComparisonExpression();
2554
        }
2555
2556 305
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_EMPTY) {
2557 4
            return $this->EmptyCollectionComparisonExpression();
2558
        }
2559
2560 301
        return $this->ComparisonExpression();
2561
    }
2562
2563
    /**
2564
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2565
     *
2566
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2567
     */
2568 4
    public function EmptyCollectionComparisonExpression()
2569
    {
2570 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2571 4
            $this->CollectionValuedPathExpression()
2572
        );
2573 4
        $this->match(Lexer::T_IS);
2574
2575 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2576 2
            $this->match(Lexer::T_NOT);
2577 2
            $emptyCollectionCompExpr->not = true;
2578
        }
2579
2580 4
        $this->match(Lexer::T_EMPTY);
2581
2582 4
        return $emptyCollectionCompExpr;
2583
    }
2584
2585
    /**
2586
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2587
     *
2588
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2589
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2590
     *
2591
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2592
     */
2593 8
    public function CollectionMemberExpression()
2594
    {
2595 8
        $not        = false;
2596 8
        $entityExpr = $this->EntityExpression();
2597
2598 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2599
            $this->match(Lexer::T_NOT);
2600
2601
            $not = true;
2602
        }
2603
2604 8
        $this->match(Lexer::T_MEMBER);
2605
2606 8
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2607 8
            $this->match(Lexer::T_OF);
2608
        }
2609
2610 8
        $collMemberExpr      = new AST\CollectionMemberExpression(
2611 8
            $entityExpr,
2612 8
            $this->CollectionValuedPathExpression()
2613
        );
2614 8
        $collMemberExpr->not = $not;
2615
2616 8
        return $collMemberExpr;
2617
    }
2618
2619
    /**
2620
     * Literal ::= string | char | integer | float | boolean
2621
     *
2622
     * @return \Doctrine\ORM\Query\AST\Literal
2623
     */
2624 186
    public function Literal()
2625
    {
2626 186
        switch ($this->lexer->lookahead['type']) {
2627
            case Lexer::T_STRING:
2628 48
                $this->match(Lexer::T_STRING);
2629
2630 48
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2631
            case Lexer::T_INTEGER:
2632
            case Lexer::T_FLOAT:
2633 139
                $this->match(
2634 139
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2635
                );
2636
2637 139
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2638
            case Lexer::T_TRUE:
2639
            case Lexer::T_FALSE:
2640 8
                $this->match(
2641 8
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2642
                );
2643
2644 8
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2645
            default:
2646
                $this->syntaxError('Literal');
2647
        }
2648
    }
2649
2650
    /**
2651
     * InParameter ::= Literal | InputParameter
2652
     *
2653
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2654
     */
2655 26
    public function InParameter()
2656
    {
2657 26
        if ($this->lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) {
2658 14
            return $this->InputParameter();
2659
        }
2660
2661 12
        return $this->Literal();
2662
    }
2663
2664
    /**
2665
     * InputParameter ::= PositionalParameter | NamedParameter
2666
     *
2667
     * @return \Doctrine\ORM\Query\AST\InputParameter
2668
     */
2669 168
    public function InputParameter()
2670
    {
2671 168
        $this->match(Lexer::T_INPUT_PARAMETER);
2672
2673 168
        return new AST\InputParameter($this->lexer->token['value']);
2674
    }
2675
2676
    /**
2677
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2678
     *
2679
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2680
     */
2681 335
    public function ArithmeticExpression()
2682
    {
2683 335
        $expr = new AST\ArithmeticExpression;
2684
2685 335
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2686 19
            $peek = $this->lexer->glimpse();
2687
2688 19
            if ($peek['type'] === Lexer::T_SELECT) {
2689 7
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2690 7
                $expr->subselect = $this->Subselect();
2691 7
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2692
2693 7
                return $expr;
2694
            }
2695
        }
2696
2697 335
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2698
2699 335
        return $expr;
2700
    }
2701
2702
    /**
2703
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2704
     *
2705
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2706
     */
2707 440
    public function SimpleArithmeticExpression()
2708
    {
2709 440
        $terms   = [];
2710 440
        $terms[] = $this->ArithmeticTerm();
2711
2712 440
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2713 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2714
2715 21
            $terms[] = $this->lexer->token['value'];
2716 21
            $terms[] = $this->ArithmeticTerm();
2717
        }
2718
2719
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2720
        // if only one AST\ArithmeticTerm is defined
2721 440
        if (\count($terms) === 1) {
2722 435
            return $terms[0];
2723
        }
2724
2725 21
        return new AST\SimpleArithmeticExpression($terms);
2726
    }
2727
2728
    /**
2729
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2730
     *
2731
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2732
     */
2733 440
    public function ArithmeticTerm()
2734
    {
2735 440
        $factors   = [];
2736 440
        $factors[] = $this->ArithmeticFactor();
2737
2738 440
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2739 53
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2740
2741 53
            $factors[] = $this->lexer->token['value'];
2742 53
            $factors[] = $this->ArithmeticFactor();
2743
        }
2744
2745
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2746
        // if only one AST\ArithmeticFactor is defined
2747 440
        if (\count($factors) === 1) {
2748 412
            return $factors[0];
2749
        }
2750
2751 53
        return new AST\ArithmeticTerm($factors);
2752
    }
2753
2754
    /**
2755
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2756
     *
2757
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2758
     */
2759 440
    public function ArithmeticFactor()
2760
    {
2761 440
        $sign   = null;
2762 440
        $isPlus = $this->lexer->isNextToken(Lexer::T_PLUS);
2763
2764 440
        if ($isPlus || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2765 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2766 3
            $sign = $isPlus;
2767
        }
2768
2769 440
        $primary = $this->ArithmeticPrimary();
2770
2771
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2772
        // if only one AST\ArithmeticPrimary is defined
2773 440
        if ($sign === null) {
2774 439
            return $primary;
2775
        }
2776
2777 3
        return new AST\ArithmeticFactor($primary, $sign);
2778
    }
2779
2780
    /**
2781
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2782
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2783
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2784
     *          | InputParameter | CaseExpression
2785
     */
2786 453
    public function ArithmeticPrimary()
2787
    {
2788 453
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2789 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2790
2791 25
            $expr = $this->SimpleArithmeticExpression();
2792
2793 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2794
2795 25
            return new AST\ParenthesisExpression($expr);
2796
        }
2797
2798 453
        switch ($this->lexer->lookahead['type']) {
2799
            case Lexer::T_COALESCE:
2800
            case Lexer::T_NULLIF:
2801
            case Lexer::T_CASE:
2802 4
                return $this->CaseExpression();
2803
2804
            case Lexer::T_IDENTIFIER:
2805 423
                $peek = $this->lexer->glimpse();
2806
2807 423
                if ($peek['value'] === '(') {
2808 41
                    return $this->FunctionDeclaration();
2809
                }
2810
2811 394
                if ($peek['value'] === '.') {
2812 383
                    return $this->SingleValuedPathExpression();
2813
                }
2814
2815 46
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2816 10
                    return $this->ResultVariable();
2817
                }
2818
2819 38
                return $this->StateFieldPathExpression();
2820
2821
            case Lexer::T_INPUT_PARAMETER:
2822 149
                return $this->InputParameter();
2823
2824
            default:
2825 180
                $peek = $this->lexer->glimpse();
2826
2827 180
                if ($peek['value'] === '(') {
2828 18
                    return $this->FunctionDeclaration();
2829
                }
2830
2831 176
                return $this->Literal();
2832
        }
2833
    }
2834
2835
    /**
2836
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2837
     *
2838
     * @return \Doctrine\ORM\Query\AST\Subselect |
2839
     *         string
2840
     */
2841 14
    public function StringExpression()
2842
    {
2843 14
        $peek = $this->lexer->glimpse();
2844
2845
        // Subselect
2846 14
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2847
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2848
            $expr = $this->Subselect();
2849
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2850
2851
            return $expr;
2852
        }
2853
2854
        // ResultVariable (string)
2855 14
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2856 14
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2857 2
            return $this->ResultVariable();
2858
        }
2859
2860 12
        return $this->StringPrimary();
2861
    }
2862
2863
    /**
2864
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2865
     */
2866 63
    public function StringPrimary()
2867
    {
2868 63
        $lookaheadType = $this->lexer->lookahead['type'];
2869
2870 63
        switch ($lookaheadType) {
2871
            case Lexer::T_IDENTIFIER:
2872 35
                $peek = $this->lexer->glimpse();
2873
2874 35
                if ($peek['value'] === '.') {
2875 35
                    return $this->StateFieldPathExpression();
2876
                }
2877
2878 8
                if ($peek['value'] === '(') {
2879
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2880 8
                    return $this->FunctionDeclaration();
2881
                }
2882
2883
                $this->syntaxError("'.' or '('");
2884
                break;
2885
2886
            case Lexer::T_STRING:
2887 45
                $this->match(Lexer::T_STRING);
2888
2889 45
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2890
2891
            case Lexer::T_INPUT_PARAMETER:
2892 2
                return $this->InputParameter();
2893
2894
            case Lexer::T_CASE:
2895
            case Lexer::T_COALESCE:
2896
            case Lexer::T_NULLIF:
2897
                return $this->CaseExpression();
2898
        }
2899
2900
        $this->syntaxError(
2901
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2902
        );
2903
    }
2904
2905
    /**
2906
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2907
     *
2908
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2909
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2910
     */
2911 8
    public function EntityExpression()
2912
    {
2913 8
        $glimpse = $this->lexer->glimpse();
2914
2915 8
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2916 1
            return $this->SingleValuedAssociationPathExpression();
2917
        }
2918
2919 7
        return $this->SimpleEntityExpression();
2920
    }
2921
2922
    /**
2923
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2924
     *
2925
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2926
     */
2927 7
    public function SimpleEntityExpression()
2928
    {
2929 7
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2930 5
            return $this->InputParameter();
2931
        }
2932
2933 2
        return $this->StateFieldPathExpression();
2934
    }
2935
2936
    /**
2937
     * AggregateExpression ::=
2938
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2939
     *
2940
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2941
     */
2942 88
    public function AggregateExpression()
2943
    {
2944 88
        $lookaheadType = $this->lexer->lookahead['type'];
2945 88
        $isDistinct    = false;
2946
2947 88
        if (! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM], true)) {
2948
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2949
        }
2950
2951 88
        $this->match($lookaheadType);
2952 88
        $functionName = $this->lexer->token['value'];
2953 88
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2954
2955 88
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2956 3
            $this->match(Lexer::T_DISTINCT);
2957 3
            $isDistinct = true;
2958
        }
2959
2960 88
        $pathExp = $this->SimpleArithmeticExpression();
2961
2962 88
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2963
2964 88
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2965
    }
2966
2967
    /**
2968
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2969
     *
2970
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2971
     */
2972 3
    public function QuantifiedExpression()
2973
    {
2974 3
        $lookaheadType = $this->lexer->lookahead['type'];
2975 3
        $value         = $this->lexer->lookahead['value'];
2976
2977 3
        if (! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME], true)) {
2978
            $this->syntaxError('ALL, ANY or SOME');
2979
        }
2980
2981 3
        $this->match($lookaheadType);
2982 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2983
2984 3
        $qExpr       = new AST\QuantifiedExpression($this->Subselect());
2985 3
        $qExpr->type = $value;
2986
2987 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2988
2989 3
        return $qExpr;
2990
    }
2991
2992
    /**
2993
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
2994
     *
2995
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
2996
     */
2997 8
    public function BetweenExpression()
2998
    {
2999 8
        $not        = false;
3000 8
        $arithExpr1 = $this->ArithmeticExpression();
3001
3002 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3003 3
            $this->match(Lexer::T_NOT);
3004 3
            $not = true;
3005
        }
3006
3007 8
        $this->match(Lexer::T_BETWEEN);
3008 8
        $arithExpr2 = $this->ArithmeticExpression();
3009 8
        $this->match(Lexer::T_AND);
3010 8
        $arithExpr3 = $this->ArithmeticExpression();
3011
3012 8
        $betweenExpr      = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3013 8
        $betweenExpr->not = $not;
3014
3015 8
        return $betweenExpr;
3016
    }
3017
3018
    /**
3019
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3020
     *
3021
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3022
     */
3023 301
    public function ComparisonExpression()
3024
    {
3025 301
        $this->lexer->glimpse();
3026
3027 301
        $leftExpr  = $this->ArithmeticExpression();
3028 301
        $operator  = $this->ComparisonOperator();
3029 301
        $rightExpr = ($this->isNextAllAnySome())
3030 3
            ? $this->QuantifiedExpression()
3031 301
            : $this->ArithmeticExpression();
3032
3033 299
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3034
    }
3035
3036
    /**
3037
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3038
     *
3039
     * @return \Doctrine\ORM\Query\AST\InExpression
3040
     */
3041 35
    public function InExpression()
3042
    {
3043 35
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3044
3045 35
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3046 6
            $this->match(Lexer::T_NOT);
3047 6
            $inExpression->not = true;
3048
        }
3049
3050 35
        $this->match(Lexer::T_IN);
3051 35
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3052
3053 35
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3054 9
            $inExpression->subselect = $this->Subselect();
3055
        } else {
3056 26
            $literals   = [];
3057 26
            $literals[] = $this->InParameter();
3058
3059 26
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3060 16
                $this->match(Lexer::T_COMMA);
3061 16
                $literals[] = $this->InParameter();
3062
            }
3063
3064 26
            $inExpression->literals = $literals;
3065
        }
3066
3067 34
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3068
3069 34
        return $inExpression;
3070
    }
3071
3072
    /**
3073
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3074
     *
3075
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3076
     */
3077 17
    public function InstanceOfExpression()
3078
    {
3079 17
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3080
3081 17
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3082 1
            $this->match(Lexer::T_NOT);
3083 1
            $instanceOfExpression->not = true;
3084
        }
3085
3086 17
        $this->match(Lexer::T_INSTANCE);
3087 17
        $this->match(Lexer::T_OF);
3088
3089 17
        $exprValues = [];
3090
3091 17
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3092 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3093
3094 2
            $exprValues[] = $this->InstanceOfParameter();
3095
3096 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3097 2
                $this->match(Lexer::T_COMMA);
3098
3099 2
                $exprValues[] = $this->InstanceOfParameter();
3100
            }
3101
3102 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3103
3104 2
            $instanceOfExpression->value = $exprValues;
3105
3106 2
            return $instanceOfExpression;
3107
        }
3108
3109 15
        $exprValues[] = $this->InstanceOfParameter();
3110
3111 15
        $instanceOfExpression->value = $exprValues;
3112
3113 15
        return $instanceOfExpression;
3114
    }
3115
3116
    /**
3117
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3118
     *
3119
     * @return mixed
3120
     */
3121 17
    public function InstanceOfParameter()
3122
    {
3123 17
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3124 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3125
3126 6
            return new AST\InputParameter($this->lexer->token['value']);
3127
        }
3128
3129 11
        $abstractSchemaName = $this->AbstractSchemaName();
3130
3131 11
        $this->validateAbstractSchemaName($abstractSchemaName);
3132
3133 11
        return $abstractSchemaName;
3134
    }
3135
3136
    /**
3137
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3138
     *
3139
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3140
     */
3141 14
    public function LikeExpression()
3142
    {
3143 14
        $stringExpr = $this->StringExpression();
3144 14
        $not        = false;
3145
3146 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3147 3
            $this->match(Lexer::T_NOT);
3148 3
            $not = true;
3149
        }
3150
3151 14
        $this->match(Lexer::T_LIKE);
3152
3153 14
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3154 3
            $this->match(Lexer::T_INPUT_PARAMETER);
3155 3
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3156
        } else {
3157 12
            $stringPattern = $this->StringPrimary();
3158
        }
3159
3160 14
        $escapeChar = null;
3161
3162 14
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3163 2
            $this->match(Lexer::T_ESCAPE);
3164 2
            $this->match(Lexer::T_STRING);
3165
3166 2
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3167
        }
3168
3169 14
        $likeExpr      = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
3170 14
        $likeExpr->not = $not;
3171
3172 14
        return $likeExpr;
3173
    }
3174
3175
    /**
3176
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3177
     *
3178
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3179
     */
3180 13
    public function NullComparisonExpression()
3181
    {
3182
        switch (true) {
3183 13
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3184
                $this->match(Lexer::T_INPUT_PARAMETER);
3185
3186
                $expr = new AST\InputParameter($this->lexer->token['value']);
3187
                break;
3188
3189 13
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3190 1
                $expr = $this->NullIfExpression();
3191 1
                break;
3192
3193 13
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3194 1
                $expr = $this->CoalesceExpression();
3195 1
                break;
3196
3197 13
            case $this->isFunction():
3198 2
                $expr = $this->FunctionDeclaration();
3199 2
                break;
3200
3201
            default:
3202
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3203 12
                $glimpse = $this->lexer->glimpse();
3204
3205 12
                if ($glimpse['type'] === Lexer::T_DOT) {
3206 8
                    $expr = $this->SingleValuedPathExpression();
3207
3208
                    // Leave switch statement
3209 8
                    break;
3210
                }
3211
3212 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3213
3214
                // Validate existing component
3215 4
                if (! isset($this->queryComponents[$lookaheadValue])) {
3216
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3217
                }
3218
3219
                // Validate SingleValuedPathExpression (ie.: "product")
3220 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3221 1
                    $expr = $this->SingleValuedPathExpression();
3222 1
                    break;
3223
                }
3224
3225
                // Validating ResultVariable
3226 3
                if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3227
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3228
                }
3229
3230 3
                $expr = $this->ResultVariable();
3231 3
                break;
3232
        }
3233
3234 13
        $nullCompExpr = new AST\NullComparisonExpression($expr);
3235
3236 13
        $this->match(Lexer::T_IS);
3237
3238 13
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3239 5
            $this->match(Lexer::T_NOT);
3240
3241 5
            $nullCompExpr->not = true;
3242
        }
3243
3244 13
        $this->match(Lexer::T_NULL);
3245
3246 13
        return $nullCompExpr;
3247
    }
3248
3249
    /**
3250
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3251
     *
3252
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
3253
     */
3254 7
    public function ExistsExpression()
3255
    {
3256 7
        $not = false;
3257
3258 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3259
            $this->match(Lexer::T_NOT);
3260
            $not = true;
3261
        }
3262
3263 7
        $this->match(Lexer::T_EXISTS);
3264 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3265
3266 7
        $existsExpression      = new AST\ExistsExpression($this->Subselect());
3267 7
        $existsExpression->not = $not;
3268
3269 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3270
3271 7
        return $existsExpression;
3272
    }
3273
3274
    /**
3275
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3276
     *
3277
     * @return string
3278
     */
3279 301
    public function ComparisonOperator()
3280
    {
3281 301
        switch ($this->lexer->lookahead['value']) {
3282
            case '=':
3283 250
                $this->match(Lexer::T_EQUALS);
3284
3285 250
                return '=';
3286
3287
            case '<':
3288 17
                $this->match(Lexer::T_LOWER_THAN);
3289 17
                $operator = '<';
3290
3291 17
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3292 5
                    $this->match(Lexer::T_EQUALS);
3293 5
                    $operator .= '=';
3294 12
                } elseif ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3295 3
                    $this->match(Lexer::T_GREATER_THAN);
3296 3
                    $operator .= '>';
3297
                }
3298
3299 17
                return $operator;
3300
3301
            case '>':
3302 47
                $this->match(Lexer::T_GREATER_THAN);
3303 47
                $operator = '>';
3304
3305 47
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3306 6
                    $this->match(Lexer::T_EQUALS);
3307 6
                    $operator .= '=';
3308
                }
3309
3310 47
                return $operator;
3311
3312
            case '!':
3313 6
                $this->match(Lexer::T_NEGATE);
3314 6
                $this->match(Lexer::T_EQUALS);
3315
3316 6
                return '<>';
3317
3318
            default:
3319
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
3320
        }
3321
    }
3322
3323
    /**
3324
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3325
     *
3326
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3327
     */
3328 156
    public function FunctionDeclaration()
3329
    {
3330 156
        $token    = $this->lexer->lookahead;
3331 156
        $funcName = strtolower($token['value']);
3332
3333 156
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3334
3335
        // Check for custom functions functions first!
3336 156
        switch (true) {
3337
            case $customFunctionDeclaration !== null:
3338 4
                return $customFunctionDeclaration;
3339
3340 152
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3341 33
                return $this->FunctionsReturningStrings();
3342
3343 124
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3344 109
                return $this->FunctionsReturningNumerics();
3345
3346 16
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3347 16
                return $this->FunctionsReturningDatetime();
3348
3349
            default:
3350
                $this->syntaxError('known function', $token);
3351
        }
3352
    }
3353
3354
    /**
3355
     * Helper function for FunctionDeclaration grammar rule.
3356
     *
3357
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3358
     */
3359 156
    private function CustomFunctionDeclaration()
3360
    {
3361 156
        $token    = $this->lexer->lookahead;
3362 156
        $funcName = strtolower($token['value']);
3363
3364
        // Check for custom functions afterwards
3365 156
        $config = $this->em->getConfiguration();
3366
3367 156
        switch (true) {
3368 156
            case ($config->getCustomStringFunction($funcName) !== null):
3369 3
                return $this->CustomFunctionsReturningStrings();
3370
3371 154
            case ($config->getCustomNumericFunction($funcName) !== null):
3372 2
                return $this->CustomFunctionsReturningNumerics();
3373
3374 152
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3375
                return $this->CustomFunctionsReturningDatetime();
3376
3377
            default:
3378 152
                return null;
3379
        }
3380
    }
3381
3382
    /**
3383
     * FunctionsReturningNumerics ::=
3384
     *      "LENGTH" "(" StringPrimary ")" |
3385
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3386
     *      "ABS" "(" SimpleArithmeticExpression ")" |
3387
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
3388
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3389
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
3390
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3391
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3392
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3393
     *
3394
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3395
     */
3396 109
    public function FunctionsReturningNumerics()
3397
    {
3398 109
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3399 109
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3400
3401 109
        $function = new $funcClass($funcNameLower);
3402 109
        $function->parse($this);
3403
3404 109
        return $function;
3405
    }
3406
3407
    /**
3408
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3409
     */
3410 2
    public function CustomFunctionsReturningNumerics()
3411
    {
3412
        // getCustomNumericFunction is case-insensitive
3413 2
        $functionName  = strtolower($this->lexer->lookahead['value']);
3414 2
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3415
3416 2
        $function = is_string($functionClass)
3417 1
            ? new $functionClass($functionName)
3418 2
            : call_user_func($functionClass, $functionName);
3419
3420 2
        $function->parse($this);
3421
3422 2
        return $function;
3423
    }
3424
3425
    /**
3426
     * FunctionsReturningDateTime ::=
3427
     *     "CURRENT_DATE" |
3428
     *     "CURRENT_TIME" |
3429
     *     "CURRENT_TIMESTAMP" |
3430
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3431
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3432
     *
3433
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3434
     */
3435 16
    public function FunctionsReturningDatetime()
3436
    {
3437 16
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3438 16
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3439
3440 16
        $function = new $funcClass($funcNameLower);
3441 16
        $function->parse($this);
3442
3443 16
        return $function;
3444
    }
3445
3446
    /**
3447
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3448
     */
3449
    public function CustomFunctionsReturningDatetime()
3450
    {
3451
        // getCustomDatetimeFunction is case-insensitive
3452
        $functionName  = $this->lexer->lookahead['value'];
3453
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3454
3455
        $function = is_string($functionClass)
3456
            ? new $functionClass($functionName)
3457
            : call_user_func($functionClass, $functionName);
3458
3459
        $function->parse($this);
3460
3461
        return $function;
3462
    }
3463
3464
    /**
3465
     * FunctionsReturningStrings ::=
3466
     *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3467
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3468
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3469
     *   "LOWER" "(" StringPrimary ")" |
3470
     *   "UPPER" "(" StringPrimary ")" |
3471
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3472
     *
3473
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3474
     */
3475 33
    public function FunctionsReturningStrings()
3476
    {
3477 33
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3478 33
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3479
3480 33
        $function = new $funcClass($funcNameLower);
3481 33
        $function->parse($this);
3482
3483 33
        return $function;
3484
    }
3485
3486
    /**
3487
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3488
     */
3489 3
    public function CustomFunctionsReturningStrings()
3490
    {
3491
        // getCustomStringFunction is case-insensitive
3492 3
        $functionName  = $this->lexer->lookahead['value'];
3493 3
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3494
3495 3
        $function = is_string($functionClass)
3496 2
            ? new $functionClass($functionName)
3497 3
            : call_user_func($functionClass, $functionName);
3498
3499 3
        $function->parse($this);
3500
3501 3
        return $function;
3502
    }
3503
}
3504