Failed Conditions
Pull Request — develop (#6947)
by Filippo
10:01
created

Parser::CustomFunctionDeclaration()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0092

Importance

Changes 0
Metric Value
cc 4
eloc 12
nc 4
nop 0
dl 0
loc 20
ccs 11
cts 12
cp 0.9167
crap 4.0092
rs 9.2
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 174 and the first side effect is on line 25.

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