Failed Conditions
Pull Request — master (#6959)
by Matthew
19:32
created

Parser::SimpleEntityExpression()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use Doctrine\Common\Persistence\Mapping\MappingException;
8
use Doctrine\ORM\Mapping\AssociationMetadata;
9
use Doctrine\ORM\Mapping\FieldMetadata;
10
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
11
use Doctrine\ORM\Query;
12
use Doctrine\ORM\Query\AST\Functions;
13
use Doctrine\ORM\Query\AST\Node;
14
15
/**
16
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
17
 * Parses a DQL query, reports any errors in it, and generates an AST.
18
 */
19
class Parser
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
20
{
21
    /**
22
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
23
     *
24
     * @var string[]
25
     */
26
    private static $_STRING_FUNCTIONS = [
27
        'concat'    => Functions\ConcatFunction::class,
28
        'substring' => Functions\SubstringFunction::class,
29
        'trim'      => Functions\TrimFunction::class,
30
        'lower'     => Functions\LowerFunction::class,
31
        'upper'     => Functions\UpperFunction::class,
32
        'identity'  => Functions\IdentityFunction::class,
33
    ];
34
35
    /**
36
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
37
     *
38
     * @var string[]
39
     */
40
    private static $_NUMERIC_FUNCTIONS = [
41
        'length'    => Functions\LengthFunction::class,
42
        'locate'    => Functions\LocateFunction::class,
43
        'abs'       => Functions\AbsFunction::class,
44
        'sqrt'      => Functions\SqrtFunction::class,
45
        'mod'       => Functions\ModFunction::class,
46
        'size'      => Functions\SizeFunction::class,
47
        'date_diff' => Functions\DateDiffFunction::class,
48
        'bit_and'   => Functions\BitAndFunction::class,
49
        'bit_or'    => Functions\BitOrFunction::class,
50
51
        // Aggregate functions
52
        'min'       => Functions\MinFunction::class,
53
        'max'       => Functions\MaxFunction::class,
54
        'avg'       => Functions\AvgFunction::class,
55
        'sum'       => Functions\SumFunction::class,
56
        'count'     => Functions\CountFunction::class,
57
    ];
58
59
    /**
60
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
61
     *
62
     * @var string[]
63
     */
64
    private static $_DATETIME_FUNCTIONS = [
65
        'current_date'      => Functions\CurrentDateFunction::class,
66
        'current_time'      => Functions\CurrentTimeFunction::class,
67
        'current_timestamp' => Functions\CurrentTimestampFunction::class,
68
        'date_add'          => Functions\DateAddFunction::class,
69
        'date_sub'          => Functions\DateSubFunction::class,
70
    ];
71
72
    /*
73
     * Expressions that were encountered during parsing of identifiers and expressions
74
     * and still need to be validated.
75
     */
76
77
    /**
78
     * @var mixed[][]
79
     */
80
    private $deferredIdentificationVariables = [];
81
82
    /**
83
     * @var mixed[][]
84
     */
85
    private $deferredPartialObjectExpressions = [];
86
87
    /**
88
     * @var mixed[][]
89
     */
90
    private $deferredPathExpressions = [];
91
92
    /**
93
     * @var mixed[][]
94
     */
95
    private $deferredResultVariables = [];
96
97
    /**
98
     * @var mixed[][]
99
     */
100
    private $deferredNewObjectExpressions = [];
101
102
    /**
103
     * The lexer.
104
     *
105
     * @var \Doctrine\ORM\Query\Lexer
106
     */
107
    private $lexer;
108
109
    /**
110
     * The parser result.
111
     *
112
     * @var \Doctrine\ORM\Query\ParserResult
113
     */
114
    private $parserResult;
115
116
    /**
117
     * The EntityManager.
118
     *
119
     * @var \Doctrine\ORM\EntityManagerInterface
120
     */
121
    private $em;
122
123
    /**
124
     * The Query to parse.
125
     *
126
     * @var Query
127
     */
128
    private $query;
129
130
    /**
131
     * Map of declared query components in the parsed query.
132
     *
133
     * @var mixed[][]
134
     */
135
    private $queryComponents = [];
136
137
    /**
138
     * Keeps the nesting level of defined ResultVariables.
139
     *
140
     * @var int
141
     */
142
    private $nestingLevel = 0;
143
144
    /**
145
     * Any additional custom tree walkers that modify the AST.
146
     *
147
     * @var string[]
148
     */
149
    private $customTreeWalkers = [];
150
151
    /**
152
     * The custom last tree walker, if any, that is responsible for producing the output.
153
     *
154
     * @var TreeWalker
155
     */
156
    private $customOutputWalker;
157
158
    /**
159
     * @var Node[]
160
     */
161
    private $identVariableExpressions = [];
162
163
    /**
164
     * Creates a new query parser object.
165
     *
166
     * @param Query $query The Query to parse.
167
     */
168 779
    public function __construct(Query $query)
169
    {
170 779
        $this->query        = $query;
171 779
        $this->em           = $query->getEntityManager();
172 779
        $this->lexer        = new Lexer($query->getDQL());
173 779
        $this->parserResult = new ParserResult();
174 779
    }
175
176
    /**
177
     * Sets a custom tree walker that produces output.
178
     * This tree walker will be run last over the AST, after any other walkers.
179
     *
180
     * @param string $className
181
     */
182 122
    public function setCustomOutputTreeWalker($className)
183
    {
184 122
        $this->customOutputWalker = $className;
0 ignored issues
show
Documentation Bug introduced by
It seems like $className of type string is incompatible with the declared type Doctrine\ORM\Query\TreeWalker of property $customOutputWalker.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
185 122
    }
186
187
    /**
188
     * Adds a custom tree walker for modifying the AST.
189
     *
190
     * @param string $className
191
     */
192
    public function addCustomTreeWalker($className)
193
    {
194
        $this->customTreeWalkers[] = $className;
195
    }
196
197
    /**
198
     * Gets the lexer used by the parser.
199
     *
200
     * @return \Doctrine\ORM\Query\Lexer
201
     */
202 31
    public function getLexer()
203
    {
204 31
        return $this->lexer;
205
    }
206
207
    /**
208
     * Gets the ParserResult that is being filled with information during parsing.
209
     *
210
     * @return \Doctrine\ORM\Query\ParserResult
211
     */
212
    public function getParserResult()
213
    {
214
        return $this->parserResult;
215
    }
216
217
    /**
218
     * Gets the EntityManager used by the parser.
219
     *
220
     * @return \Doctrine\ORM\EntityManagerInterface
221
     */
222 16
    public function getEntityManager()
223
    {
224 16
        return $this->em;
225
    }
226
227
    /**
228
     * Parses and builds AST for the given Query.
229
     *
230
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
231
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
232
     *         \Doctrine\ORM\Query\AST\DeleteStatement
233
     */
234 779
    public function getAST()
235
    {
236
        // Parse & build AST
237 779
        $AST = $this->QueryLanguage();
238
239
        // Process any deferred validations of some nodes in the AST.
240
        // This also allows post-processing of the AST for modification purposes.
241 737
        $this->processDeferredIdentificationVariables();
242
243 735
        if ($this->deferredPartialObjectExpressions) {
244 9
            $this->processDeferredPartialObjectExpressions();
245
        }
246
247 734
        if ($this->deferredPathExpressions) {
248 537
            $this->processDeferredPathExpressions();
249
        }
250
251 732
        if ($this->deferredResultVariables) {
252 32
            $this->processDeferredResultVariables();
253
        }
254
255 732
        if ($this->deferredNewObjectExpressions) {
256 28
            $this->processDeferredNewObjectExpressions($AST);
257
        }
258
259 728
        $this->processRootEntityAliasSelected();
260
261
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
262 727
        $this->fixIdentificationVariableOrder($AST);
263
264 722
        return $AST;
265
    }
266
267
    /**
268
     * Attempts to match the given token with the current lookahead token.
269
     *
270
     * If they match, updates the lookahead token; otherwise raises a syntax
271
     * error.
272
     *
273
     * @param int $token The token type.
274
     *
275
     * @throws QueryException If the tokens don't match.
276
     */
277 790
    public function match($token)
278
    {
279 790
        $lookaheadType = $this->lexer->lookahead['type'];
280
281
        // Short-circuit on first condition, usually types match
282 790
        if ($lookaheadType !== $token) {
283
            // If parameter is not identifier (1-99) must be exact match
284 21
            if ($token < Lexer::T_IDENTIFIER) {
285 3
                $this->syntaxError($this->lexer->getLiteral($token));
286
            }
287
288
            // If parameter is keyword (200+) must be exact match
289 18
            if ($token > Lexer::T_IDENTIFIER) {
290 7
                $this->syntaxError($this->lexer->getLiteral($token));
291
            }
292
293
            // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
294 11
            if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
295 8
                $this->syntaxError($this->lexer->getLiteral($token));
296
            }
297
        }
298
299 783
        $this->lexer->moveNext();
300 783
    }
301
302
    /**
303
     * Frees this parser, enabling it to be reused.
304
     *
305
     * @param bool $deep     Whether to clean peek and reset errors.
306
     * @param int  $position Position to reset.
307
     */
308
    public function free($deep = false, $position = 0)
309
    {
310
        // WARNING! Use this method with care. It resets the scanner!
311
        $this->lexer->resetPosition($position);
312
313
        // Deep = true cleans peek and also any previously defined errors
314
        if ($deep) {
315
            $this->lexer->resetPeek();
316
        }
317
318
        $this->lexer->token     = null;
319
        $this->lexer->lookahead = null;
320
    }
321
322
    /**
323
     * Parses a query string.
324
     *
325
     * @return ParserResult
326
     */
327 779
    public function parse()
328
    {
329 779
        $AST = $this->getAST();
330
331 722
        $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
332 722
        if ($customWalkers !== false) {
333 93
            $this->customTreeWalkers = $customWalkers;
334
        }
335
336 722
        $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER);
337
338 722
        if ($customOutputWalker !== false) {
339 77
            $this->customOutputWalker = $customOutputWalker;
340
        }
341
342
        // Run any custom tree walkers over the AST
343 722
        if ($this->customTreeWalkers) {
344 92
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
345
346 92
            foreach ($this->customTreeWalkers as $walker) {
347 92
                $treeWalkerChain->addTreeWalker($walker);
348
            }
349
350
            switch (true) {
351 92
                case ($AST instanceof AST\UpdateStatement):
352
                    $treeWalkerChain->walkUpdateStatement($AST);
353
                    break;
354
355 92
                case ($AST instanceof AST\DeleteStatement):
356
                    $treeWalkerChain->walkDeleteStatement($AST);
357
                    break;
358
359 92
                case ($AST instanceof AST\SelectStatement):
360
                default:
361 92
                    $treeWalkerChain->walkSelectStatement($AST);
362
            }
363
364 87
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
365
        }
366
367 717
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
368 717
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
369
370
        // Assign an SQL executor to the parser result
371 717
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
372
373 709
        return $this->parserResult;
374
    }
375
376
    /**
377
     * Fixes order of identification variables.
378
     *
379
     * They have to appear in the select clause in the same order as the
380
     * declarations (from ... x join ... y join ... z ...) appear in the query
381
     * as the hydration process relies on that order for proper operation.
382
     *
383
     * @param AST\SelectStatement $AST
384
     *
385
     * @return void
386
     */
387 722
    private function fixIdentificationVariableOrder(AST\SelectStatement $AST)
388
    {
389 722
        if (count($this->identVariableExpressions) <= 1) {
390 547
            return;
391
        }
392
393 180
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
394 180
            if (! isset($this->identVariableExpressions[$dqlAlias])) {
395 8
                continue;
396
            }
397
398 180
            $expr = $this->identVariableExpressions[$dqlAlias];
399 180
            $key  = array_search($expr, $AST->selectClause->selectExpressions);
400
401 180
            unset($AST->selectClause->selectExpressions[$key]);
402
403 180
            $AST->selectClause->selectExpressions[] = $expr;
404
        }
405 180
    }
406
407
    /**
408
     * Generates a new syntax error.
409
     *
410
     * @param string       $expected Expected string.
411
     * @param mixed[]|null $token    Got token.
412
     *
413
     * @throws \Doctrine\ORM\Query\QueryException
414
     */
415 18
    public function syntaxError($expected = '', $token = null)
416
    {
417 18
        if ($token === null) {
418 15
            $token = $this->lexer->lookahead;
419
        }
420
421 18
        $tokenPos = $token['position'] ?? '-1';
422
423 18
        $message  = sprintf('line 0, col %d: Error: ', $tokenPos);
424 18
        $message .= ($expected !== '') ? sprintf('Expected %s, got ', $expected) : 'Unexpected ';
425 18
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : sprintf("'%s'", $token['value']);
426
427 18
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
428
    }
429
430
    /**
431
     * Generates a new semantical error.
432
     *
433
     * @param string       $message Optional message.
434
     * @param mixed[]|null $token   Optional token.
435
     *
436
     * @throws \Doctrine\ORM\Query\QueryException
437
     */
438 33
    public function semanticalError($message = '', $token = null, ?\Throwable $previousFailure = null)
439
    {
440 33
        if ($token === null) {
441 2
            $token = $this->lexer->lookahead;
442
        }
443
444
        // Minimum exposed chars ahead of token
445 33
        $distance = 12;
446
447
        // Find a position of a final word to display in error string
448 33
        $dql    = $this->query->getDQL();
449 33
        $length = strlen($dql);
450 33
        $pos    = $token['position'] + $distance;
451 33
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
452 33
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
453
454 33
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
455 33
        $tokenStr = substr($dql, (int) $token['position'], $length);
456
457
        // Building informative message
458 33
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
459
460 33
        throw QueryException::semanticalError(
461 33
            $message,
462 33
            QueryException::dqlError($this->query->getDQL(), $previousFailure)
463
        );
464
    }
465
466
    /**
467
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
468
     *
469
     * @param bool $resetPeek Reset peek after finding the closing parenthesis.
470
     *
471
     * @return mixed[]
472
     */
473 163
    private function peekBeyondClosingParenthesis($resetPeek = true)
474
    {
475 163
        $token        = $this->lexer->peek();
476 163
        $numUnmatched = 1;
477
478 163
        while ($numUnmatched > 0 && $token !== null) {
479 162
            switch ($token['type']) {
480
                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...
481 42
                    ++$numUnmatched;
482 42
                    break;
483
484
                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...
485 162
                    --$numUnmatched;
486 162
                    break;
487
488
                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...
489
                    // Do nothing
490
            }
491
492 162
            $token = $this->lexer->peek();
493
        }
494
495 163
        if ($resetPeek) {
496 144
            $this->lexer->resetPeek();
497
        }
498
499 163
        return $token;
500
    }
501
502
    /**
503
     * Checks if the given token indicates a mathematical operator.
504
     *
505
     * @param mixed[] $token
506
     *
507
     * @return bool TRUE if the token is a mathematical operator, FALSE otherwise.
508
     */
509 353
    private function isMathOperator($token)
510
    {
511 353
        return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
512
    }
513
514
    /**
515
     * Checks if the next-next (after lookahead) token starts a function.
516
     *
517
     * @return bool TRUE if the next-next tokens start a function, FALSE otherwise.
518
     */
519 396
    private function isFunction()
520
    {
521 396
        $lookaheadType = $this->lexer->lookahead['type'];
522 396
        $peek          = $this->lexer->peek();
523
524 396
        $this->lexer->resetPeek();
525
526 396
        return $lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS;
527
    }
528
529
    /**
530
     * Checks whether the given token type indicates an aggregate function.
531
     *
532
     * @param int $tokenType
533
     *
534
     * @return bool TRUE if the token type is an aggregate function, FALSE otherwise.
535
     */
536 1
    private function isAggregateFunction($tokenType)
537
    {
538 1
        return in_array($tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT]);
539
    }
540
541
    /**
542
     * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
543
     *
544
     * @return bool
545
     */
546 257
    private function isNextAllAnySome()
547
    {
548 257
        return in_array($this->lexer->lookahead['type'], [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME]);
549
    }
550
551
    /**
552
     * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
553
     * It must exist in query components list.
554
     */
555 737
    private function processDeferredIdentificationVariables()
556
    {
557 737
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
558 722
            $identVariable = $deferredItem['expression'];
559
560
            // Check if IdentificationVariable exists in queryComponents
561 722
            if (! isset($this->queryComponents[$identVariable])) {
562 1
                $this->semanticalError(
563 1
                    sprintf("'%s' is not defined.", $identVariable),
564 1
                    $deferredItem['token']
565
                );
566
            }
567
568 722
            $qComp = $this->queryComponents[$identVariable];
569
570
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
571 722
            if (! isset($qComp['metadata'])) {
572
                $this->semanticalError(
573
                    sprintf("'%s' does not point to a Class.", $identVariable),
574
                    $deferredItem['token']
575
                );
576
            }
577
578
            // Validate if identification variable nesting level is lower or equal than the current one
579 722
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
580 1
                $this->semanticalError(
581 1
                    sprintf("'%s' is used outside the scope of its declaration.", $identVariable),
582 722
                    $deferredItem['token']
583
                );
584
            }
585
        }
586 735
    }
587
588
    /**
589
     * Validates that the given <tt>NewObjectExpression</tt>.
590
     *
591
     * @param \Doctrine\ORM\Query\AST\SelectStatement $AST
592
     *
593
     * @return void
594
     */
595 28
    private function processDeferredNewObjectExpressions($AST)
596
    {
597 28
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
598 28
            $expression    = $deferredItem['expression'];
599 28
            $token         = $deferredItem['token'];
600 28
            $className     = $expression->className;
601 28
            $args          = $expression->args;
602 28
            $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
603
604
            // If the namespace is not given then assumes the first FROM entity namespace
605 28
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
606 11
                $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
607 11
                $fqcn      = $namespace . '\\' . $className;
608
609 11
                if (class_exists($fqcn)) {
610 11
                    $expression->className = $fqcn;
611 11
                    $className             = $fqcn;
612
                }
613
            }
614
615 28
            if (! class_exists($className)) {
616 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
617
            }
618
619 27
            $class = new \ReflectionClass($className);
620
621 27
            if (! $class->isInstantiable()) {
622 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
623
            }
624
625 26
            if ($class->getConstructor() === null) {
626 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
627
            }
628
629 25
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
630 25
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
631
            }
632
        }
633 24
    }
634
635
    /**
636
     * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
637
     * It must exist in query components list.
638
     */
639 9
    private function processDeferredPartialObjectExpressions()
640
    {
641 9
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
642 9
            $expr  = $deferredItem['expression'];
643 9
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
644
645 9
            foreach ($expr->partialFieldSet as $field) {
646 9
                $property = $class->getProperty($field);
647
648 9
                if ($property instanceof FieldMetadata ||
649 9
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
650 9
                    continue;
651
                }
652
653
                $this->semanticalError(
654
                    sprintf("There is no mapped field named '%s' on class %s.", $field, $class->getClassName()),
655
                    $deferredItem['token']
656
                );
657
            }
658
659 9
            if (array_intersect($class->identifier, $expr->partialFieldSet) !== $class->identifier) {
660 1
                $this->semanticalError(
661 1
                    sprintf('The partial field selection of class %s must contain the identifier.', $class->getClassName()),
662 9
                    $deferredItem['token']
663
                );
664
            }
665
        }
666 8
    }
667
668
    /**
669
     * Validates that the given <tt>ResultVariable</tt> is semantically correct.
670
     * It must exist in query components list.
671
     */
672 32
    private function processDeferredResultVariables()
673
    {
674 32
        foreach ($this->deferredResultVariables as $deferredItem) {
675 32
            $resultVariable = $deferredItem['expression'];
676
677
            // Check if ResultVariable exists in queryComponents
678 32
            if (! isset($this->queryComponents[$resultVariable])) {
679
                $this->semanticalError(
680
                    sprintf("'%s' is not defined.", $resultVariable),
681
                    $deferredItem['token']
682
                );
683
            }
684
685 32
            $qComp = $this->queryComponents[$resultVariable];
686
687
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
688 32
            if (! isset($qComp['resultVariable'])) {
689
                $this->semanticalError(
690
                    sprintf("'%s' does not point to a ResultVariable.", $resultVariable),
691
                    $deferredItem['token']
692
                );
693
            }
694
695
            // Validate if identification variable nesting level is lower or equal than the current one
696 32
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
697
                $this->semanticalError(
698
                    sprintf("'%s' is used outside the scope of its declaration.", $resultVariable),
699 32
                    $deferredItem['token']
700
                );
701
            }
702
        }
703 32
    }
704
705
    /**
706
     * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
707
     *
708
     * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
709
     * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
710
     * StateFieldPathExpression              ::= IdentificationVariable "." StateField
711
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
712
     * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
713
     */
714 537
    private function processDeferredPathExpressions()
715
    {
716 537
        foreach ($this->deferredPathExpressions as $deferredItem) {
717 537
            $pathExpression = $deferredItem['expression'];
718
719 537
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
720 537
            $class = $qComp['metadata'];
721 537
            $field = $pathExpression->field;
722
723 537
            if ($field === null) {
724 36
                $field = $pathExpression->field = $class->identifier[0];
725
            }
726
727 537
            $property = $class->getProperty($field);
728
729
            // Check if field or association exists
730 537
            if (! $property) {
731
                $this->semanticalError(
732
                    'Class ' . $class->getClassName() . ' has no field or association named ' . $field,
733
                    $deferredItem['token']
734
                );
735
            }
736
737 537
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
738
739 537
            if ($property instanceof AssociationMetadata) {
740 83
                $fieldType = $property instanceof ToOneAssociationMetadata
741 63
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
742 83
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
743
                ;
744
            }
745
746
            // Validate if PathExpression is one of the expected types
747 537
            $expectedType = $pathExpression->expectedType;
748
749 537
            if (! ($expectedType & $fieldType)) {
750
                // We need to recognize which was expected type(s)
751 2
                $expectedStringTypes = [];
752
753
                // Validate state field type
754 2
                if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
755 1
                    $expectedStringTypes[] = 'StateFieldPathExpression';
756
                }
757
758
                // Validate single valued association (*-to-one)
759 2
                if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
760 2
                    $expectedStringTypes[] = 'SingleValuedAssociationField';
761
                }
762
763
                // Validate single valued association (*-to-many)
764 2
                if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
765
                    $expectedStringTypes[] = 'CollectionValuedAssociationField';
766
                }
767
768
                // Build the error message
769 2
                $semanticalError  = 'Invalid PathExpression. ';
770 2
                $semanticalError .= \count($expectedStringTypes) === 1
771 1
                    ? 'Must be a ' . $expectedStringTypes[0] . '.'
772 2
                    : implode(' or ', $expectedStringTypes) . ' expected.';
773
774 2
                $this->semanticalError($semanticalError, $deferredItem['token']);
775
            }
776
777
            // We need to force the type in PathExpression
778 535
            $pathExpression->type = $fieldType;
779
        }
780 535
    }
781
782 728
    private function processRootEntityAliasSelected()
783
    {
784 728
        if (! $this->identVariableExpressions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->identVariableExpressions of type Doctrine\ORM\Query\AST\Node[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
785 173
            return;
786
        }
787
788 564
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
789 564
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
790 564
                return;
791
            }
792
        }
793
794 1
        $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
795
    }
796
797
    /**
798
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
799
     *
800
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
801
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
802
     *         \Doctrine\ORM\Query\AST\DeleteStatement
803
     */
804 779
    public function QueryLanguage()
805
    {
806 779
        $statement = null;
807
808 779
        $this->lexer->moveNext();
809
810 779
        switch ($this->lexer->lookahead['type']) {
811
            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...
812 773
                $statement = $this->SelectStatement();
813 735
                break;
814
815
            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...
816 4
                $statement = $this->UpdateStatement();
817 4
                break;
818
819
            case Lexer::T_DELETE:
820 4
                $statement = $this->DeleteStatement();
821 3
                break;
822
823
            default:
824 2
                $this->syntaxError('SELECT, UPDATE or DELETE');
825
                break;
826
        }
827
828
        // Check for end of string
829 740
        if ($this->lexer->lookahead !== null) {
830 3
            $this->syntaxError('end of string');
831
        }
832
833 737
        return $statement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statement also could return the type Doctrine\ORM\Query\AST\U...ery\AST\DeleteStatement which is incompatible with the documented return type Doctrine\ORM\Query\AST\SelectStatement.
Loading history...
834
    }
835
836
    /**
837
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
838
     *
839
     * @return \Doctrine\ORM\Query\AST\SelectStatement
840
     */
841 773
    public function SelectStatement()
842
    {
843 773
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
844
845 739
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
846 736
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
847 735
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
848 735
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
849
850 735
        return $selectStatement;
851
    }
852
853
    /**
854
     * UpdateStatement ::= UpdateClause [WhereClause]
855
     *
856
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
857
     */
858 4
    public function UpdateStatement()
859
    {
860 4
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
861
862 4
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
863
864 4
        return $updateStatement;
865
    }
866
867
    /**
868
     * DeleteStatement ::= DeleteClause [WhereClause]
869
     *
870
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
871
     */
872 4
    public function DeleteStatement()
873
    {
874 4
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
875
876 3
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
877
878 3
        return $deleteStatement;
879
    }
880
881
    /**
882
     * IdentificationVariable ::= identifier
883
     *
884
     * @return string
885
     */
886 753
    public function IdentificationVariable()
887
    {
888 753
        $this->match(Lexer::T_IDENTIFIER);
889
890 753
        $identVariable = $this->lexer->token['value'];
891
892 753
        $this->deferredIdentificationVariables[] = [
893 753
            'expression'   => $identVariable,
894 753
            'nestingLevel' => $this->nestingLevel,
895 753
            'token'        => $this->lexer->token,
896
        ];
897
898 753
        return $identVariable;
899
    }
900
901
    /**
902
     * AliasIdentificationVariable = identifier
903
     *
904
     * @return string
905
     */
906 748
    public function AliasIdentificationVariable()
907
    {
908 748
        $this->match(Lexer::T_IDENTIFIER);
909
910 748
        $aliasIdentVariable = $this->lexer->token['value'];
911 748
        $exists             = isset($this->queryComponents[$aliasIdentVariable]);
912
913 748
        if ($exists) {
914 2
            $this->semanticalError(sprintf("'%s' is already defined.", $aliasIdentVariable), $this->lexer->token);
915
        }
916
917 748
        return $aliasIdentVariable;
918
    }
919
920
    /**
921
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
922
     *
923
     * @return string
924
     */
925 769
    public function AbstractSchemaName()
926
    {
927 769
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
928 752
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
929
930 752
            $schemaName = $this->lexer->token['value'];
931 28
        } elseif ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
932 19
            $this->match(Lexer::T_IDENTIFIER);
933
934 19
            $schemaName = $this->lexer->token['value'];
935
        } else {
936 10
            $this->match(Lexer::T_ALIASED_NAME);
937
938 9
            list($namespaceAlias, $simpleClassName) = explode(':', $this->lexer->token['value']);
939
940 9
            $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
941
        }
942
943 768
        return $schemaName;
944
    }
945
946
    /**
947
     * Validates an AbstractSchemaName, making sure the class exists.
948
     *
949
     * @param string $schemaName The name to validate.
950
     *
951
     * @throws QueryException If the name does not exist.
952
     */
953 763
    private function validateAbstractSchemaName($schemaName) : void
954
    {
955 763
        if (class_exists($schemaName, true) || interface_exists($schemaName, true)) {
956 748
            return;
957
        }
958
959
        try {
960 16
            $this->getEntityManager()->getClassMetadata($schemaName);
961
962
            return;
963 16
        } catch (MappingException $mappingException) {
964 16
            $this->semanticalError(
965 16
                \sprintf('Class %s could not be mapped', $schemaName),
966 16
                $this->lexer->token
967
            );
968
        }
969
970
        $this->semanticalError(sprintf("Class '%s' is not defined.", $schemaName), $this->lexer->token);
971
    }
972
973
    /**
974
     * AliasResultVariable ::= identifier
975
     *
976
     * @return string
977
     */
978 130
    public function AliasResultVariable()
979
    {
980 130
        $this->match(Lexer::T_IDENTIFIER);
981
982 126
        $resultVariable = $this->lexer->token['value'];
983 126
        $exists         = isset($this->queryComponents[$resultVariable]);
984
985 126
        if ($exists) {
986 2
            $this->semanticalError(sprintf("'%s' is already defined.", $resultVariable), $this->lexer->token);
987
        }
988
989 126
        return $resultVariable;
990
    }
991
992
    /**
993
     * ResultVariable ::= identifier
994
     *
995
     * @return string
996
     */
997 32
    public function ResultVariable()
998
    {
999 32
        $this->match(Lexer::T_IDENTIFIER);
1000
1001 32
        $resultVariable = $this->lexer->token['value'];
1002
1003
        // Defer ResultVariable validation
1004 32
        $this->deferredResultVariables[] = [
1005 32
            'expression'   => $resultVariable,
1006 32
            'nestingLevel' => $this->nestingLevel,
1007 32
            'token'        => $this->lexer->token,
1008
        ];
1009
1010 32
        return $resultVariable;
1011
    }
1012
1013
    /**
1014
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1015
     *
1016
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1017
     */
1018 258
    public function JoinAssociationPathExpression()
1019
    {
1020 258
        $identVariable = $this->IdentificationVariable();
1021
1022 258
        if (! isset($this->queryComponents[$identVariable])) {
1023
            $this->semanticalError(
1024
                'Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.'
1025
            );
1026
        }
1027
1028 258
        $this->match(Lexer::T_DOT);
1029 258
        $this->match(Lexer::T_IDENTIFIER);
1030
1031 258
        $field = $this->lexer->token['value'];
1032
1033
        // Validate association field
1034 258
        $qComp    = $this->queryComponents[$identVariable];
1035 258
        $class    = $qComp['metadata'];
1036 258
        $property = $class->getProperty($field);
1037
1038 258
        if (! ($property !== null && $property instanceof AssociationMetadata)) {
1039
            $this->semanticalError('Class ' . $class->getClassName() . ' has no association named ' . $field);
1040
        }
1041
1042 258
        return new AST\JoinAssociationPathExpression($identVariable, $field);
1043
    }
1044
1045
    /**
1046
     * Parses an arbitrary path expression and defers semantical validation
1047
     * based on expected types.
1048
     *
1049
     * PathExpression ::= IdentificationVariable {"." identifier}*
1050
     *
1051
     * @param int $expectedTypes
1052
     *
1053
     * @return \Doctrine\ORM\Query\AST\PathExpression
1054
     */
1055 547
    public function PathExpression($expectedTypes)
1056
    {
1057 547
        $identVariable = $this->IdentificationVariable();
1058 547
        $field         = null;
1059
1060 547
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1061 543
            $this->match(Lexer::T_DOT);
1062 543
            $this->match(Lexer::T_IDENTIFIER);
1063
1064 543
            $field = $this->lexer->token['value'];
1065
1066 543
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1067
                $this->match(Lexer::T_DOT);
1068
                $this->match(Lexer::T_IDENTIFIER);
1069
                $field .= '.' . $this->lexer->token['value'];
1070
            }
1071
        }
1072
1073
        // Creating AST node
1074 547
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1075
1076
        // Defer PathExpression validation if requested to be deferred
1077 547
        $this->deferredPathExpressions[] = [
1078 547
            'expression'   => $pathExpr,
1079 547
            'nestingLevel' => $this->nestingLevel,
1080 547
            'token'        => $this->lexer->token,
1081
        ];
1082
1083 547
        return $pathExpr;
1084
    }
1085
1086
    /**
1087
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
1088
     *
1089
     * @return \Doctrine\ORM\Query\AST\PathExpression
1090
     */
1091
    public function AssociationPathExpression()
1092
    {
1093
        return $this->PathExpression(
1094
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
1095
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
1096
        );
1097
    }
1098
1099
    /**
1100
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1101
     *
1102
     * @return \Doctrine\ORM\Query\AST\PathExpression
1103
     */
1104 461
    public function SingleValuedPathExpression()
1105
    {
1106 461
        return $this->PathExpression(
1107 461
            AST\PathExpression::TYPE_STATE_FIELD |
1108 461
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1109
        );
1110
    }
1111
1112
    /**
1113
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1114
     *
1115
     * @return \Doctrine\ORM\Query\AST\PathExpression
1116
     */
1117 200
    public function StateFieldPathExpression()
1118
    {
1119 200
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1120
    }
1121
1122
    /**
1123
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1124
     *
1125
     * @return \Doctrine\ORM\Query\AST\PathExpression
1126
     */
1127 9
    public function SingleValuedAssociationPathExpression()
1128
    {
1129 9
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1130
    }
1131
1132
    /**
1133
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1134
     *
1135
     * @return \Doctrine\ORM\Query\AST\PathExpression
1136
     */
1137 20
    public function CollectionValuedPathExpression()
1138
    {
1139 20
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1140
    }
1141
1142
    /**
1143
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1144
     *
1145
     * @return \Doctrine\ORM\Query\AST\SelectClause
1146
     */
1147 773
    public function SelectClause()
1148
    {
1149 773
        $isDistinct = false;
1150 773
        $this->match(Lexer::T_SELECT);
1151
1152
        // Check for DISTINCT
1153 773
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1154 6
            $this->match(Lexer::T_DISTINCT);
1155
1156 6
            $isDistinct = true;
1157
        }
1158
1159
        // Process SelectExpressions (1..N)
1160 773
        $selectExpressions   = [];
1161 773
        $selectExpressions[] = $this->SelectExpression();
1162
1163 765
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1164 296
            $this->match(Lexer::T_COMMA);
1165
1166 296
            $selectExpressions[] = $this->SelectExpression();
1167
        }
1168
1169 764
        return new AST\SelectClause($selectExpressions, $isDistinct);
1170
    }
1171
1172
    /**
1173
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1174
     *
1175
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1176
     */
1177 48
    public function SimpleSelectClause()
1178
    {
1179 48
        $isDistinct = false;
1180 48
        $this->match(Lexer::T_SELECT);
1181
1182 48
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1183
            $this->match(Lexer::T_DISTINCT);
1184
1185
            $isDistinct = true;
1186
        }
1187
1188 48
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1189
    }
1190
1191
    /**
1192
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1193
     *
1194
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1195
     */
1196 4
    public function UpdateClause()
1197
    {
1198 4
        $this->match(Lexer::T_UPDATE);
1199
1200 4
        $token              = $this->lexer->lookahead;
1201 4
        $abstractSchemaName = $this->AbstractSchemaName();
1202
1203 4
        $this->validateAbstractSchemaName($abstractSchemaName);
1204
1205 4
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1206
            $this->match(Lexer::T_AS);
1207
        }
1208
1209 4
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1210
1211 4
        $class = $this->em->getClassMetadata($abstractSchemaName);
1212
1213
        // Building queryComponent
1214
        $queryComponent = [
1215 4
            'metadata'     => $class,
1216
            'parent'       => null,
1217
            'relation'     => null,
1218
            'map'          => null,
1219 4
            'nestingLevel' => $this->nestingLevel,
1220 4
            'token'        => $token,
1221
        ];
1222
1223 4
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1224
1225 4
        $this->match(Lexer::T_SET);
1226
1227 4
        $updateItems   = [];
1228 4
        $updateItems[] = $this->UpdateItem();
1229
1230 4
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1231 1
            $this->match(Lexer::T_COMMA);
1232
1233 1
            $updateItems[] = $this->UpdateItem();
1234
        }
1235
1236 4
        $updateClause                              = new AST\UpdateClause($abstractSchemaName, $updateItems);
1237 4
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1238
1239 4
        return $updateClause;
1240
    }
1241
1242
    /**
1243
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1244
     *
1245
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1246
     */
1247 4
    public function DeleteClause()
1248
    {
1249 4
        $this->match(Lexer::T_DELETE);
1250
1251 4
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1252 2
            $this->match(Lexer::T_FROM);
1253
        }
1254
1255 4
        $token              = $this->lexer->lookahead;
1256 4
        $abstractSchemaName = $this->AbstractSchemaName();
1257
1258 4
        $this->validateAbstractSchemaName($abstractSchemaName);
1259
1260 4
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1261
1262 4
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1263
            $this->match(Lexer::T_AS);
1264
        }
1265
1266 4
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1267
1268 3
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1269 3
        $class                                     = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1270
1271
        // Building queryComponent
1272
        $queryComponent = [
1273 3
            'metadata'     => $class,
1274
            'parent'       => null,
1275
            'relation'     => null,
1276
            'map'          => null,
1277 3
            'nestingLevel' => $this->nestingLevel,
1278 3
            'token'        => $token,
1279
        ];
1280
1281 3
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1282
1283 3
        return $deleteClause;
1284
    }
1285
1286
    /**
1287
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1288
     *
1289
     * @return \Doctrine\ORM\Query\AST\FromClause
1290
     */
1291 764
    public function FromClause()
1292
    {
1293 764
        $this->match(Lexer::T_FROM);
1294
1295 759
        $identificationVariableDeclarations   = [];
1296 759
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1297
1298 739
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1299 7
            $this->match(Lexer::T_COMMA);
1300
1301 7
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1302
        }
1303
1304 739
        return new AST\FromClause($identificationVariableDeclarations);
1305
    }
1306
1307
    /**
1308
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1309
     *
1310
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1311
     */
1312 48
    public function SubselectFromClause()
1313
    {
1314 48
        $this->match(Lexer::T_FROM);
1315
1316 48
        $identificationVariables   = [];
1317 48
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1318
1319 47
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1320
            $this->match(Lexer::T_COMMA);
1321
1322
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1323
        }
1324
1325 47
        return new AST\SubselectFromClause($identificationVariables);
1326
    }
1327
1328
    /**
1329
     * WhereClause ::= "WHERE" ConditionalExpression
1330
     *
1331
     * @return \Doctrine\ORM\Query\AST\WhereClause
1332
     */
1333 286
    public function WhereClause()
1334
    {
1335 286
        $this->match(Lexer::T_WHERE);
1336
1337 286
        return new AST\WhereClause($this->ConditionalExpression());
1338
    }
1339
1340
    /**
1341
     * HavingClause ::= "HAVING" ConditionalExpression
1342
     *
1343
     * @return \Doctrine\ORM\Query\AST\HavingClause
1344
     */
1345 21
    public function HavingClause()
1346
    {
1347 21
        $this->match(Lexer::T_HAVING);
1348
1349 21
        return new AST\HavingClause($this->ConditionalExpression());
1350
    }
1351
1352
    /**
1353
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1354
     *
1355
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1356
     */
1357 33
    public function GroupByClause()
1358
    {
1359 33
        $this->match(Lexer::T_GROUP);
1360 33
        $this->match(Lexer::T_BY);
1361
1362 33
        $groupByItems = [$this->GroupByItem()];
1363
1364 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1365 8
            $this->match(Lexer::T_COMMA);
1366
1367 8
            $groupByItems[] = $this->GroupByItem();
1368
        }
1369
1370 32
        return new AST\GroupByClause($groupByItems);
1371
    }
1372
1373
    /**
1374
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1375
     *
1376
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1377
     */
1378 182
    public function OrderByClause()
1379
    {
1380 182
        $this->match(Lexer::T_ORDER);
1381 182
        $this->match(Lexer::T_BY);
1382
1383 182
        $orderByItems   = [];
1384 182
        $orderByItems[] = $this->OrderByItem();
1385
1386 182
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1387 15
            $this->match(Lexer::T_COMMA);
1388
1389 15
            $orderByItems[] = $this->OrderByItem();
1390
        }
1391
1392 182
        return new AST\OrderByClause($orderByItems);
1393
    }
1394
1395
    /**
1396
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1397
     *
1398
     * @return \Doctrine\ORM\Query\AST\Subselect
1399
     */
1400 48
    public function Subselect()
1401
    {
1402
        // Increase query nesting level
1403 48
        $this->nestingLevel++;
1404
1405 48
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1406
1407 47
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1408 47
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1409 47
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1410 47
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1411
1412
        // Decrease query nesting level
1413 47
        $this->nestingLevel--;
1414
1415 47
        return $subselect;
1416
    }
1417
1418
    /**
1419
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1420
     *
1421
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1422
     */
1423 4
    public function UpdateItem()
1424
    {
1425 4
        $pathExpr = $this->SingleValuedPathExpression();
1426
1427 4
        $this->match(Lexer::T_EQUALS);
1428
1429 4
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1430
1431 4
        return $updateItem;
1432
    }
1433
1434
    /**
1435
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1436
     *
1437
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1438
     */
1439 33
    public function GroupByItem()
1440
    {
1441
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1442 33
        $glimpse = $this->lexer->glimpse();
1443
1444 33
        if ($glimpse['type'] === Lexer::T_DOT) {
1445 14
            return $this->SingleValuedPathExpression();
1446
        }
1447
1448
        // Still need to decide between IdentificationVariable or ResultVariable
1449 19
        $lookaheadValue = $this->lexer->lookahead['value'];
1450
1451 19
        if (! isset($this->queryComponents[$lookaheadValue])) {
1452 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1453
        }
1454
1455 18
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1456 16
            ? $this->IdentificationVariable()
1457 18
            : $this->ResultVariable();
1458
    }
1459
1460
    /**
1461
     * OrderByItem ::= (
1462
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1463
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1464
     * ) ["ASC" | "DESC"]
1465
     *
1466
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1467
     */
1468 182
    public function OrderByItem()
1469
    {
1470 182
        $this->lexer->peek(); // lookahead => '.'
1471 182
        $this->lexer->peek(); // lookahead => token after '.'
1472
1473 182
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1474
1475 182
        $this->lexer->resetPeek();
1476
1477 182
        $glimpse = $this->lexer->glimpse();
1478
1479
        switch (true) {
1480 182
            case ($this->isFunction()):
1481 2
                $expr = $this->FunctionDeclaration();
1482 2
                break;
1483
1484 180
            case ($this->isMathOperator($peek)):
1485 25
                $expr = $this->SimpleArithmeticExpression();
1486 25
                break;
1487
1488 156
            case ($glimpse['type'] === Lexer::T_DOT):
1489 141
                $expr = $this->SingleValuedPathExpression();
1490 141
                break;
1491
1492 19
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1493 2
                $expr = $this->ScalarExpression();
1494 2
                break;
1495
1496
            default:
1497 17
                $expr = $this->ResultVariable();
1498 17
                break;
1499
        }
1500
1501 182
        $type = 'ASC';
1502 182
        $item = new AST\OrderByItem($expr);
1503
1504
        switch (true) {
1505 182
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1506 95
                $this->match(Lexer::T_DESC);
1507 95
                $type = 'DESC';
1508 95
                break;
1509
1510 154
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1511 97
                $this->match(Lexer::T_ASC);
1512 97
                break;
1513
1514
            default:
1515
                // Do nothing
1516
        }
1517
1518 182
        $item->type = $type;
1519
1520 182
        return $item;
1521
    }
1522
1523
    /**
1524
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1525
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1526
     *
1527
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1528
     * grammar that needs to be supported:
1529
     *
1530
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1531
     *
1532
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1533
     *
1534
     * @return AST\ArithmeticExpression
1535
     */
1536 4
    public function NewValue()
1537
    {
1538 4
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1539
            $this->match(Lexer::T_NULL);
1540
1541
            return null;
1542
        }
1543
1544 4
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1545 2
            $this->match(Lexer::T_INPUT_PARAMETER);
1546
1547 2
            return new AST\InputParameter($this->lexer->token['value']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Doctrine\ORM\...>lexer->token['value']) returns the type Doctrine\ORM\Query\AST\InputParameter which is incompatible with the documented return type Doctrine\ORM\Query\AST\ArithmeticExpression.
Loading history...
1548
        }
1549
1550 2
        return $this->ArithmeticExpression();
1551
    }
1552
1553
    /**
1554
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1555
     *
1556
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1557
     */
1558 760
    public function IdentificationVariableDeclaration()
1559
    {
1560 760
        $joins                    = [];
1561 760
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1562 743
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1563 7
            ? $this->IndexBy()
1564 743
            : null;
1565
1566 743
        $rangeVariableDeclaration->isRoot = true;
1567
1568 743
        while ($this->lexer->isNextToken(Lexer::T_LEFT) ||
1569 743
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1570 743
            $this->lexer->isNextToken(Lexer::T_JOIN)
1571
        ) {
1572 280
            $joins[] = $this->Join();
1573
        }
1574
1575 740
        return new AST\IdentificationVariableDeclaration(
1576 740
            $rangeVariableDeclaration,
1577 740
            $indexBy,
1578 740
            $joins
1579
        );
1580
    }
1581
1582
    /**
1583
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1584
     *
1585
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1586
     * Desired EBNF support:
1587
     *
1588
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1589
     *
1590
     * It demands that entire SQL generation to become programmatical. This is
1591
     * needed because association based subselect requires "WHERE" conditional
1592
     * expressions to be injected, but there is no scope to do that. Only scope
1593
     * accessible is "FROM", prohibiting an easy implementation without larger
1594
     * changes.}
1595
     *
1596
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1597
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1598
     */
1599 48
    public function SubselectIdentificationVariableDeclaration()
1600
    {
1601
        /*
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...
1602
        NOT YET IMPLEMENTED!
1603
1604
        $glimpse = $this->lexer->glimpse();
1605
1606
        if ($glimpse['type'] == Lexer::T_DOT) {
1607
            $associationPathExpression = $this->AssociationPathExpression();
1608
1609
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1610
                $this->match(Lexer::T_AS);
1611
            }
1612
1613
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1614
            $identificationVariable      = $associationPathExpression->identificationVariable;
1615
            $field                       = $associationPathExpression->associationField;
1616
1617
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1618
            $association = $class->getProperty($field);
1619
            $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
1620
1621
            // Building queryComponent
1622
            $joinQueryComponent = array(
1623
                'metadata'     => $targetClass,
1624
                'parent'       => $identificationVariable,
1625
                'relation'     => $association,
1626
                'map'          => null,
1627
                'nestingLevel' => $this->nestingLevel,
1628
                'token'        => $this->lexer->lookahead
1629
            );
1630
1631
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1632
1633
            return new AST\SubselectIdentificationVariableDeclaration(
1634
                $associationPathExpression, $aliasIdentificationVariable
1635
            );
1636
        }
1637
        */
1638
1639 48
        return $this->IdentificationVariableDeclaration();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->IdentificationVariableDeclaration() returns the type Doctrine\ORM\Query\AST\I...tionVariableDeclaration which is incompatible with the documented return type Doctrine\ORM\Query\AST\S...tionVariableDeclaration.
Loading history...
1640
    }
1641
1642
    /**
1643
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1644
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1645
     *          ["WITH" ConditionalExpression]
1646
     *
1647
     * @return \Doctrine\ORM\Query\AST\Join
1648
     */
1649 280
    public function Join()
1650
    {
1651
        // Check Join type
1652 280
        $joinType = AST\Join::JOIN_TYPE_INNER;
1653
1654
        switch (true) {
1655 280
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1656 68
                $this->match(Lexer::T_LEFT);
1657
1658 68
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1659
1660
                // Possible LEFT OUTER join
1661 68
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1662
                    $this->match(Lexer::T_OUTER);
1663
1664
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1665
                }
1666 68
                break;
1667
1668 215
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1669 21
                $this->match(Lexer::T_INNER);
1670 21
                break;
1671
1672
            default:
1673
                // Do nothing
1674
        }
1675
1676 280
        $this->match(Lexer::T_JOIN);
1677
1678 280
        $next            = $this->lexer->glimpse();
1679 280
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1680 277
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1681 277
        $join            = new AST\Join($joinType, $joinDeclaration);
1682
1683
        // Describe non-root join declaration
1684 277
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1685 22
            $joinDeclaration->isRoot = false;
1686
        }
1687
1688
        // Check for ad-hoc Join conditions
1689 277
        if ($adhocConditions) {
1690 25
            $this->match(Lexer::T_WITH);
1691
1692 25
            $join->conditionalExpression = $this->ConditionalExpression();
1693
        }
1694
1695 277
        return $join;
1696
    }
1697
1698
    /**
1699
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1700
     *
1701
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1702
     *
1703
     * @throws QueryException
1704
     */
1705 760
    public function RangeVariableDeclaration()
1706
    {
1707 760
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
1708 2
            $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1709
        }
1710
1711 759
        $abstractSchemaName = $this->AbstractSchemaName();
1712
1713 758
        $this->validateAbstractSchemaName($abstractSchemaName);
1714
1715 743
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1716 6
            $this->match(Lexer::T_AS);
1717
        }
1718
1719 743
        $token                       = $this->lexer->lookahead;
1720 743
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1721 743
        $classMetadata               = $this->em->getClassMetadata($abstractSchemaName);
1722
1723
        // Building queryComponent
1724
        $queryComponent = [
1725 743
            'metadata'     => $classMetadata,
1726
            'parent'       => null,
1727
            'relation'     => null,
1728
            'map'          => null,
1729 743
            'nestingLevel' => $this->nestingLevel,
1730 743
            'token'        => $token,
1731
        ];
1732
1733 743
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1734
1735 743
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1736
    }
1737
1738
    /**
1739
     * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1740
     *
1741
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1742
     */
1743 258
    public function JoinAssociationDeclaration()
1744
    {
1745 258
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1746
1747 258
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1748 5
            $this->match(Lexer::T_AS);
1749
        }
1750
1751 258
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1752 256
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1753
1754 256
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1755 256
        $field                  = $joinAssociationPathExpression->associationField;
1756
1757 256
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1758 256
        $association = $class->getProperty($field);
1759 256
        $targetClass = $this->em->getClassMetadata($association->getTargetEntity());
1760
1761
        // Building queryComponent
1762
        $joinQueryComponent = [
1763 256
            'metadata'     => $targetClass,
1764 256
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1765 256
            'relation'     => $association,
1766
            'map'          => null,
1767 256
            'nestingLevel' => $this->nestingLevel,
1768 256
            'token'        => $this->lexer->lookahead,
1769
        ];
1770
1771 256
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1772
1773 256
        return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Doctrine\ORM\...tionVariable, $indexBy) returns the type Doctrine\ORM\Query\AST\JoinAssociationDeclaration which is incompatible with the documented return type Doctrine\ORM\Query\AST\J...sociationPathExpression.
Loading history...
1774
    }
1775
1776
    /**
1777
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1778
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1779
     *
1780
     * @return \Doctrine\ORM\Query\AST\PartialObjectExpression
1781
     */
1782 9
    public function PartialObjectExpression()
1783
    {
1784 9
        $this->match(Lexer::T_PARTIAL);
1785
1786 9
        $partialFieldSet = [];
1787
1788 9
        $identificationVariable = $this->IdentificationVariable();
1789
1790 9
        $this->match(Lexer::T_DOT);
1791 9
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1792 9
        $this->match(Lexer::T_IDENTIFIER);
1793
1794 9
        $field = $this->lexer->token['value'];
1795
1796
        // First field in partial expression might be embeddable property
1797 9
        while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1798
            $this->match(Lexer::T_DOT);
1799
            $this->match(Lexer::T_IDENTIFIER);
1800
            $field .= '.' . $this->lexer->token['value'];
1801
        }
1802
1803 9
        $partialFieldSet[] = $field;
1804
1805 9
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1806 7
            $this->match(Lexer::T_COMMA);
1807 7
            $this->match(Lexer::T_IDENTIFIER);
1808
1809 7
            $field = $this->lexer->token['value'];
1810
1811 7
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1812
                $this->match(Lexer::T_DOT);
1813
                $this->match(Lexer::T_IDENTIFIER);
1814
                $field .= '.' . $this->lexer->token['value'];
1815
            }
1816
1817 7
            $partialFieldSet[] = $field;
1818
        }
1819
1820 9
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1821
1822 9
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1823
1824
        // Defer PartialObjectExpression validation
1825 9
        $this->deferredPartialObjectExpressions[] = [
1826 9
            'expression'   => $partialObjectExpression,
1827 9
            'nestingLevel' => $this->nestingLevel,
1828 9
            'token'        => $this->lexer->token,
1829
        ];
1830
1831 9
        return $partialObjectExpression;
1832
    }
1833
1834
    /**
1835
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1836
     *
1837
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1838
     */
1839 28
    public function NewObjectExpression()
1840
    {
1841 28
        $this->match(Lexer::T_NEW);
1842
1843 28
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1844 28
        $token     = $this->lexer->token;
1845
1846 28
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1847
1848 28
        $args[] = $this->NewObjectArg();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$args was never initialized. Although not strictly required by PHP, it is generally a good practice to add $args = array(); before regardless.
Loading history...
1849
1850 28
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1851 24
            $this->match(Lexer::T_COMMA);
1852
1853 24
            $args[] = $this->NewObjectArg();
1854
        }
1855
1856 28
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1857
1858 28
        $expression = new AST\NewObjectExpression($className, $args);
1859
1860
        // Defer NewObjectExpression validation
1861 28
        $this->deferredNewObjectExpressions[] = [
1862 28
            'token'        => $token,
1863 28
            'expression'   => $expression,
1864 28
            'nestingLevel' => $this->nestingLevel,
1865
        ];
1866
1867 28
        return $expression;
1868
    }
1869
1870
    /**
1871
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1872
     *
1873
     * @return mixed
1874
     */
1875 28
    public function NewObjectArg()
1876
    {
1877 28
        $token = $this->lexer->lookahead;
1878 28
        $peek  = $this->lexer->glimpse();
1879
1880 28
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1881 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1882 2
            $expression = $this->Subselect();
1883 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1884
1885 2
            return $expression;
1886
        }
1887
1888 28
        return $this->ScalarExpression();
1889
    }
1890
1891
    /**
1892
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1893
     *
1894
     * @return \Doctrine\ORM\Query\AST\IndexBy
1895
     */
1896 11
    public function IndexBy()
1897
    {
1898 11
        $this->match(Lexer::T_INDEX);
1899 11
        $this->match(Lexer::T_BY);
1900 11
        $pathExpr = $this->StateFieldPathExpression();
1901
1902
        // Add the INDEX BY info to the query component
1903 11
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1904
1905 11
        return new AST\IndexBy($pathExpr);
1906
    }
1907
1908
    /**
1909
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1910
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1911
     *                      InstanceOfExpression
1912
     *
1913
     * @return mixed One of the possible expressions or subexpressions.
1914
     */
1915 165
    public function ScalarExpression()
1916
    {
1917 165
        $lookahead = $this->lexer->lookahead['type'];
1918 165
        $peek      = $this->lexer->glimpse();
1919
1920 165
        switch (true) {
1921
            case ($lookahead === Lexer::T_INTEGER):
1922 162
            case ($lookahead === Lexer::T_FLOAT):
1923
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1924 162
            case ($lookahead === Lexer::T_MINUS):
1925 162
            case ($lookahead === Lexer::T_PLUS):
1926 17
                return $this->SimpleArithmeticExpression();
1927
1928 162
            case ($lookahead === Lexer::T_STRING):
1929 13
                return $this->StringPrimary();
1930
1931 160
            case ($lookahead === Lexer::T_TRUE):
1932 160
            case ($lookahead === Lexer::T_FALSE):
1933 3
                $this->match($lookahead);
1934
1935 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1936
1937 160
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
1938
                switch (true) {
1939 1
                    case $this->isMathOperator($peek):
1940
                        // :param + u.value
1941 1
                        return $this->SimpleArithmeticExpression();
1942
                    default:
1943
                        return $this->InputParameter();
1944
                }
1945
                // cannot get here
1946
1947 160
            case ($lookahead === Lexer::T_CASE):
1948 156
            case ($lookahead === Lexer::T_COALESCE):
1949 156
            case ($lookahead === Lexer::T_NULLIF):
1950
                // Since NULLIF and COALESCE can be identified as a function,
1951
                // we need to check these before checking for FunctionDeclaration
1952 8
                return $this->CaseExpression();
1953
1954 156
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1955 4
                return $this->SimpleArithmeticExpression();
1956
1957
            // this check must be done before checking for a filed path expression
1958 153
            case ($this->isFunction()):
1959 27
                $this->lexer->peek(); // "("
1960
1961
                switch (true) {
1962 27
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1963
                        // SUM(u.id) + COUNT(u.id)
1964 7
                        return $this->SimpleArithmeticExpression();
1965
1966
                    default:
1967
                        // IDENTITY(u)
1968 22
                        return $this->FunctionDeclaration();
1969
                }
1970
1971
                break;
1972
            // it is no function, so it must be a field path
1973 134
            case ($lookahead === Lexer::T_IDENTIFIER):
1974 134
                $this->lexer->peek(); // lookahead => '.'
1975 134
                $this->lexer->peek(); // lookahead => token after '.'
1976 134
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1977 134
                $this->lexer->resetPeek();
1978
1979 134
                if ($this->isMathOperator($peek)) {
1980 7
                    return $this->SimpleArithmeticExpression();
1981
                }
1982
1983 129
                return $this->StateFieldPathExpression();
1984
1985
            default:
1986
                $this->syntaxError();
1987
        }
1988
    }
1989
1990
    /**
1991
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
1992
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1993
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1994
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1995
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1996
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1997
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1998
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1999
     *
2000
     * @return mixed One of the possible expressions or subexpressions.
2001
     */
2002 19
    public function CaseExpression()
2003
    {
2004 19
        $lookahead = $this->lexer->lookahead['type'];
2005
2006 19
        switch ($lookahead) {
2007
            case Lexer::T_NULLIF:
2008 5
                return $this->NullIfExpression();
2009
2010
            case Lexer::T_COALESCE:
2011 2
                return $this->CoalesceExpression();
2012
2013
            case Lexer::T_CASE:
2014 14
                $this->lexer->resetPeek();
2015 14
                $peek = $this->lexer->peek();
2016
2017 14
                if ($peek['type'] === Lexer::T_WHEN) {
2018 9
                    return $this->GeneralCaseExpression();
2019
                }
2020
2021 5
                return $this->SimpleCaseExpression();
2022
2023
            default:
2024
                // Do nothing
2025
                break;
2026
        }
2027
2028
        $this->syntaxError();
2029
    }
2030
2031
    /**
2032
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2033
     *
2034
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2035
     */
2036 3
    public function CoalesceExpression()
2037
    {
2038 3
        $this->match(Lexer::T_COALESCE);
2039 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2040
2041
        // Process ScalarExpressions (1..N)
2042 3
        $scalarExpressions   = [];
2043 3
        $scalarExpressions[] = $this->ScalarExpression();
2044
2045 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2046 3
            $this->match(Lexer::T_COMMA);
2047
2048 3
            $scalarExpressions[] = $this->ScalarExpression();
2049
        }
2050
2051 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2052
2053 3
        return new AST\CoalesceExpression($scalarExpressions);
2054
    }
2055
2056
    /**
2057
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2058
     *
2059
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2060
     */
2061 5
    public function NullIfExpression()
2062
    {
2063 5
        $this->match(Lexer::T_NULLIF);
2064 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2065
2066 5
        $firstExpression = $this->ScalarExpression();
2067 5
        $this->match(Lexer::T_COMMA);
2068 5
        $secondExpression = $this->ScalarExpression();
2069
2070 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2071
2072 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2073
    }
2074
2075
    /**
2076
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2077
     *
2078
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2079
     */
2080 9
    public function GeneralCaseExpression()
2081
    {
2082 9
        $this->match(Lexer::T_CASE);
2083
2084
        // Process WhenClause (1..N)
2085 9
        $whenClauses = [];
2086
2087
        do {
2088 9
            $whenClauses[] = $this->WhenClause();
2089 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2090
2091 9
        $this->match(Lexer::T_ELSE);
2092 9
        $scalarExpression = $this->ScalarExpression();
2093 9
        $this->match(Lexer::T_END);
2094
2095 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2096
    }
2097
2098
    /**
2099
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2100
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2101
     *
2102
     * @return AST\SimpleCaseExpression
2103
     */
2104 5
    public function SimpleCaseExpression()
2105
    {
2106 5
        $this->match(Lexer::T_CASE);
2107 5
        $caseOperand = $this->StateFieldPathExpression();
2108
2109
        // Process SimpleWhenClause (1..N)
2110 5
        $simpleWhenClauses = [];
2111
2112
        do {
2113 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2114 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2115
2116 5
        $this->match(Lexer::T_ELSE);
2117 5
        $scalarExpression = $this->ScalarExpression();
2118 5
        $this->match(Lexer::T_END);
2119
2120 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2121
    }
2122
2123
    /**
2124
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2125
     *
2126
     * @return \Doctrine\ORM\Query\AST\WhenClause
2127
     */
2128 9
    public function WhenClause()
2129
    {
2130 9
        $this->match(Lexer::T_WHEN);
2131 9
        $conditionalExpression = $this->ConditionalExpression();
2132 9
        $this->match(Lexer::T_THEN);
2133
2134 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2135
    }
2136
2137
    /**
2138
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2139
     *
2140
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2141
     */
2142 5
    public function SimpleWhenClause()
2143
    {
2144 5
        $this->match(Lexer::T_WHEN);
2145 5
        $conditionalExpression = $this->ScalarExpression();
2146 5
        $this->match(Lexer::T_THEN);
2147
2148 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2149
    }
2150
2151
    /**
2152
     * SelectExpression ::= (
2153
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2154
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2155
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2156
     *
2157
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2158
     */
2159 773
    public function SelectExpression()
2160
    {
2161 773
        $expression    = null;
2162 773
        $identVariable = null;
2163 773
        $peek          = $this->lexer->glimpse();
2164 773
        $lookaheadType = $this->lexer->lookahead['type'];
2165
2166 773
        switch (true) {
2167
            // ScalarExpression (u.name)
2168 698
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2169 106
                $expression = $this->ScalarExpression();
2170 106
                break;
2171
2172
            // IdentificationVariable (u)
2173 713
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2174 589
                $expression = $identVariable = $this->IdentificationVariable();
2175 589
                break;
2176
2177
            // 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...
2178 186
            case ($lookaheadType === Lexer::T_CASE):
2179 181
            case ($lookaheadType === Lexer::T_COALESCE):
2180 179
            case ($lookaheadType === Lexer::T_NULLIF):
2181 9
                $expression = $this->CaseExpression();
2182 9
                break;
2183
2184
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2185 177
            case ($this->isFunction()):
2186 99
                $this->lexer->peek(); // "("
2187
2188
                switch (true) {
2189 99
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2190
                        // SUM(u.id) + COUNT(u.id)
2191 2
                        $expression = $this->ScalarExpression();
2192 2
                        break;
2193
2194
                    default:
2195
                        // IDENTITY(u)
2196 97
                        $expression = $this->FunctionDeclaration();
2197 97
                        break;
2198
                }
2199
2200 99
                break;
2201
2202
            // PartialObjectExpression (PARTIAL u.{id, name})
2203 79
            case ($lookaheadType === Lexer::T_PARTIAL):
2204 9
                $expression    = $this->PartialObjectExpression();
2205 9
                $identVariable = $expression->identificationVariable;
2206 9
                break;
2207
2208
            // Subselect
2209 70
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2210 23
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2211 23
                $expression = $this->Subselect();
2212 23
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2213 23
                break;
2214
2215
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2216 47
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2217 43
            case ($lookaheadType === Lexer::T_INTEGER):
2218 41
            case ($lookaheadType === Lexer::T_STRING):
2219 32
            case ($lookaheadType === Lexer::T_FLOAT):
2220
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2221 32
            case ($lookaheadType === Lexer::T_MINUS):
2222 32
            case ($lookaheadType === Lexer::T_PLUS):
2223 16
                $expression = $this->SimpleArithmeticExpression();
2224 16
                break;
2225
2226
            // 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...
2227 31
            case ($lookaheadType === Lexer::T_NEW):
2228 28
                $expression = $this->NewObjectExpression();
2229 28
                break;
2230
2231
            default:
2232 3
                $this->syntaxError(
2233 3
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2234 3
                    $this->lexer->lookahead
2235
                );
2236
        }
2237
2238
        // [["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...
2239 770
        $mustHaveAliasResultVariable = false;
2240
2241 770
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2242 121
            $this->match(Lexer::T_AS);
2243
2244 121
            $mustHaveAliasResultVariable = true;
2245
        }
2246
2247 770
        $hiddenAliasResultVariable = false;
2248
2249 770
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2250 10
            $this->match(Lexer::T_HIDDEN);
2251
2252 10
            $hiddenAliasResultVariable = true;
2253
        }
2254
2255 770
        $aliasResultVariable = null;
2256
2257 770
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2258 130
            $token               = $this->lexer->lookahead;
2259 130
            $aliasResultVariable = $this->AliasResultVariable();
2260
2261
            // Include AliasResultVariable in query components.
2262 125
            $this->queryComponents[$aliasResultVariable] = [
2263 125
                'resultVariable' => $expression,
2264 125
                'nestingLevel'   => $this->nestingLevel,
2265 125
                'token'          => $token,
2266
            ];
2267
        }
2268
2269
        // AST
2270
2271 765
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2272
2273 765
        if ($identVariable) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $identVariable of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2274 596
            $this->identVariableExpressions[$identVariable] = $expr;
2275
        }
2276
2277 765
        return $expr;
2278
    }
2279
2280
    /**
2281
     * SimpleSelectExpression ::= (
2282
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2283
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2284
     * ) [["AS"] AliasResultVariable]
2285
     *
2286
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2287
     */
2288 48
    public function SimpleSelectExpression()
2289
    {
2290 48
        $peek = $this->lexer->glimpse();
2291
2292 48
        switch ($this->lexer->lookahead['type']) {
2293
            case Lexer::T_IDENTIFIER:
2294 18
                switch (true) {
2295 18
                    case ($peek['type'] === Lexer::T_DOT):
2296 15
                        $expression = $this->StateFieldPathExpression();
2297
2298 15
                        return new AST\SimpleSelectExpression($expression);
2299
2300 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2301 2
                        $expression = $this->IdentificationVariable();
2302
2303 2
                        return new AST\SimpleSelectExpression($expression);
0 ignored issues
show
Bug introduced by
$expression of type string is incompatible with the type Doctrine\ORM\Query\AST\Node expected by parameter $expression of Doctrine\ORM\Query\AST\S...pression::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

2303
                        return new AST\SimpleSelectExpression(/** @scrutinizer ignore-type */ $expression);
Loading history...
2304
2305 1
                    case ($this->isFunction()):
2306
                        // SUM(u.id) + COUNT(u.id)
2307 1
                        if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
2308
                            return new AST\SimpleSelectExpression($this->ScalarExpression());
2309
                        }
2310
                        // COUNT(u.id)
2311 1
                        if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
2312
                            return new AST\SimpleSelectExpression($this->AggregateExpression());
2313
                        }
2314
                        // IDENTITY(u)
2315 1
                        return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2316
2317
                    default:
2318
                        // Do nothing
2319
                }
2320
                break;
2321
2322
            case Lexer::T_OPEN_PARENTHESIS:
2323 3
                if ($peek['type'] !== Lexer::T_SELECT) {
2324
                    // Shortcut: ScalarExpression => SimpleArithmeticExpression
2325 3
                    $expression = $this->SimpleArithmeticExpression();
2326
2327 3
                    return new AST\SimpleSelectExpression($expression);
2328
                }
2329
2330
                // Subselect
2331
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2332
                $expression = $this->Subselect();
2333
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2334
2335
                return new AST\SimpleSelectExpression($expression);
2336
2337
            default:
2338
                // Do nothing
2339
        }
2340
2341 28
        $this->lexer->peek();
2342
2343 28
        $expression = $this->ScalarExpression();
2344 28
        $expr       = new AST\SimpleSelectExpression($expression);
2345
2346 28
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2347 1
            $this->match(Lexer::T_AS);
2348
        }
2349
2350 28
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2351 2
            $token                             = $this->lexer->lookahead;
2352 2
            $resultVariable                    = $this->AliasResultVariable();
2353 2
            $expr->fieldIdentificationVariable = $resultVariable;
2354
2355
            // Include AliasResultVariable in query components.
2356 2
            $this->queryComponents[$resultVariable] = [
2357 2
                'resultvariable' => $expr,
2358 2
                'nestingLevel'   => $this->nestingLevel,
2359 2
                'token'          => $token,
2360
            ];
2361
        }
2362
2363 28
        return $expr;
2364
    }
2365
2366
    /**
2367
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2368
     *
2369
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2370
     */
2371 330
    public function ConditionalExpression()
2372
    {
2373 330
        $conditionalTerms   = [];
2374 330
        $conditionalTerms[] = $this->ConditionalTerm();
2375
2376 327
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2377 14
            $this->match(Lexer::T_OR);
2378
2379 14
            $conditionalTerms[] = $this->ConditionalTerm();
2380
        }
2381
2382
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2383
        // if only one AST\ConditionalTerm is defined
2384 327
        if (\count($conditionalTerms) === 1) {
2385 321
            return $conditionalTerms[0];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $conditionalTerms[0] returns the type Doctrine\ORM\Query\AST\ConditionalTerm which is incompatible with the documented return type Doctrine\ORM\Query\AST\ConditionalExpression.
Loading history...
2386
        }
2387
2388 14
        return new AST\ConditionalExpression($conditionalTerms);
2389
    }
2390
2391
    /**
2392
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2393
     *
2394
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2395
     */
2396 330
    public function ConditionalTerm()
2397
    {
2398 330
        $conditionalFactors   = [];
2399 330
        $conditionalFactors[] = $this->ConditionalFactor();
2400
2401 327
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2402 27
            $this->match(Lexer::T_AND);
2403
2404 27
            $conditionalFactors[] = $this->ConditionalFactor();
2405
        }
2406
2407
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2408
        // if only one AST\ConditionalFactor is defined
2409 327
        if (\count($conditionalFactors) === 1) {
2410 312
            return $conditionalFactors[0];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $conditionalFactors[0] returns the type Doctrine\ORM\Query\AST\ConditionalFactor which is incompatible with the documented return type Doctrine\ORM\Query\AST\ConditionalTerm.
Loading history...
2411
        }
2412
2413 27
        return new AST\ConditionalTerm($conditionalFactors);
2414
    }
2415
2416
    /**
2417
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2418
     *
2419
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2420
     */
2421 330
    public function ConditionalFactor()
2422
    {
2423 330
        $not = false;
2424
2425 330
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2426 3
            $this->match(Lexer::T_NOT);
2427
2428 3
            $not = true;
2429
        }
2430
2431 330
        $conditionalPrimary = $this->ConditionalPrimary();
2432
2433
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2434
        // if only one AST\ConditionalPrimary is defined
2435 327
        if (! $not) {
2436 326
            return $conditionalPrimary;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $conditionalPrimary returns the type Doctrine\ORM\Query\AST\ConditionalPrimary which is incompatible with the documented return type Doctrine\ORM\Query\AST\ConditionalFactor.
Loading history...
2437
        }
2438
2439 3
        $conditionalFactor      = new AST\ConditionalFactor($conditionalPrimary);
2440 3
        $conditionalFactor->not = $not;
2441
2442 3
        return $conditionalFactor;
2443
    }
2444
2445
    /**
2446
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2447
     *
2448
     * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
2449
     */
2450 330
    public function ConditionalPrimary()
2451
    {
2452 330
        $condPrimary = new AST\ConditionalPrimary;
2453
2454 330
        if (! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2455 321
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2456
2457 318
            return $condPrimary;
2458
        }
2459
2460
        // Peek beyond the matching closing parenthesis ')'
2461 22
        $peek = $this->peekBeyondClosingParenthesis();
2462
2463 22
        if (in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!=']) ||
2464 19
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
2465 22
            $this->isMathOperator($peek)) {
2466 15
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2467
2468 15
            return $condPrimary;
2469
        }
2470
2471 18
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2472 18
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2473 18
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2474
2475 18
        return $condPrimary;
2476
    }
2477
2478
    /**
2479
     * SimpleConditionalExpression ::=
2480
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2481
     *      InExpression | NullComparisonExpression | ExistsExpression |
2482
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2483
     *      InstanceOfExpression
2484
     */
2485 330
    public function SimpleConditionalExpression()
2486
    {
2487 330
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2488 7
            return $this->ExistsExpression();
2489
        }
2490
2491 330
        $token     = $this->lexer->lookahead;
2492 330
        $peek      = $this->lexer->glimpse();
2493 330
        $lookahead = $token;
2494
2495 330
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2496
            $token = $this->lexer->glimpse();
2497
        }
2498
2499 330
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2500
            // Peek beyond the matching closing parenthesis.
2501 307
            $beyond = $this->lexer->peek();
2502
2503 307
            switch ($peek['value']) {
2504
                case '(':
2505
                    // Peeks beyond the matched closing parenthesis.
2506 34
                    $token = $this->peekBeyondClosingParenthesis(false);
2507
2508 34
                    if ($token['type'] === Lexer::T_NOT) {
2509 3
                        $token = $this->lexer->peek();
2510
                    }
2511
2512 34
                    if ($token['type'] === Lexer::T_IS) {
2513 2
                        $lookahead = $this->lexer->peek();
2514
                    }
2515 34
                    break;
2516
2517
                default:
2518
                    // Peek beyond the PathExpression or InputParameter.
2519 282
                    $token = $beyond;
2520
2521 282
                    while ($token['value'] === '.') {
2522 240
                        $this->lexer->peek();
2523
2524 240
                        $token = $this->lexer->peek();
2525
                    }
2526
2527
                    // Also peek beyond a NOT if there is one.
2528 282
                    if ($token['type'] === Lexer::T_NOT) {
2529 7
                        $token = $this->lexer->peek();
2530
                    }
2531
2532
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2533 282
                    $lookahead = $this->lexer->peek();
2534
            }
2535
2536
            // Also peek beyond a NOT if there is one.
2537 307
            if ($lookahead['type'] === Lexer::T_NOT) {
2538 6
                $lookahead = $this->lexer->peek();
2539
            }
2540
2541 307
            $this->lexer->resetPeek();
2542
        }
2543
2544 330
        if ($token['type'] === Lexer::T_BETWEEN) {
2545 5
            return $this->BetweenExpression();
2546
        }
2547
2548 326
        if ($token['type'] === Lexer::T_LIKE) {
2549 12
            return $this->LikeExpression();
2550
        }
2551
2552 315
        if ($token['type'] === Lexer::T_IN) {
2553 30
            return $this->InExpression();
2554
        }
2555
2556 294
        if ($token['type'] === Lexer::T_INSTANCE) {
2557 17
            return $this->InstanceOfExpression();
2558
        }
2559
2560 277
        if ($token['type'] === Lexer::T_MEMBER) {
2561 8
            return $this->CollectionMemberExpression();
2562
        }
2563
2564 269
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2565 11
            return $this->NullComparisonExpression();
2566
        }
2567
2568 261
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_EMPTY) {
2569 4
            return $this->EmptyCollectionComparisonExpression();
2570
        }
2571
2572 257
        return $this->ComparisonExpression();
2573
    }
2574
2575
    /**
2576
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2577
     *
2578
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2579
     */
2580 4
    public function EmptyCollectionComparisonExpression()
2581
    {
2582 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2583 4
            $this->CollectionValuedPathExpression()
2584
        );
2585 4
        $this->match(Lexer::T_IS);
2586
2587 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2588 2
            $this->match(Lexer::T_NOT);
2589 2
            $emptyCollectionCompExpr->not = true;
2590
        }
2591
2592 4
        $this->match(Lexer::T_EMPTY);
2593
2594 4
        return $emptyCollectionCompExpr;
2595
    }
2596
2597
    /**
2598
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2599
     *
2600
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2601
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2602
     *
2603
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2604
     */
2605 8
    public function CollectionMemberExpression()
2606
    {
2607 8
        $not        = false;
2608 8
        $entityExpr = $this->EntityExpression();
2609
2610 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2611
            $this->match(Lexer::T_NOT);
2612
2613
            $not = true;
2614
        }
2615
2616 8
        $this->match(Lexer::T_MEMBER);
2617
2618 8
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2619 8
            $this->match(Lexer::T_OF);
2620
        }
2621
2622 8
        $collMemberExpr      = new AST\CollectionMemberExpression(
2623 8
            $entityExpr,
2624 8
            $this->CollectionValuedPathExpression()
2625
        );
2626 8
        $collMemberExpr->not = $not;
2627
2628 8
        return $collMemberExpr;
2629
    }
2630
2631
    /**
2632
     * Literal ::= string | char | integer | float | boolean
2633
     *
2634
     * @return \Doctrine\ORM\Query\AST\Literal
2635
     */
2636 167
    public function Literal()
2637
    {
2638 167
        switch ($this->lexer->lookahead['type']) {
2639
            case Lexer::T_STRING:
2640 42
                $this->match(Lexer::T_STRING);
2641
2642 42
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2643
            case Lexer::T_INTEGER:
2644
            case Lexer::T_FLOAT:
2645 125
                $this->match(
2646 125
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2647
                );
2648
2649 125
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2650
            case Lexer::T_TRUE:
2651
            case Lexer::T_FALSE:
2652 6
                $this->match(
2653 6
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2654
                );
2655
2656 6
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2657
            default:
2658
                $this->syntaxError('Literal');
2659
        }
2660
    }
2661
2662
    /**
2663
     * InParameter ::= Literal | InputParameter
2664
     *
2665
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2666
     */
2667 21
    public function InParameter()
2668
    {
2669 21
        if ($this->lexer->lookahead['type'] === Lexer::T_INPUT_PARAMETER) {
2670 11
            return $this->InputParameter();
2671
        }
2672
2673 10
        return $this->Literal();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->Literal() returns the type Doctrine\ORM\Query\AST\Literal which is incompatible with the documented return type string|Doctrine\ORM\Query\AST\InputParameter.
Loading history...
2674
    }
2675
2676
    /**
2677
     * InputParameter ::= PositionalParameter | NamedParameter
2678
     *
2679
     * @return \Doctrine\ORM\Query\AST\InputParameter
2680
     */
2681 132
    public function InputParameter()
2682
    {
2683 132
        $this->match(Lexer::T_INPUT_PARAMETER);
2684
2685 132
        return new AST\InputParameter($this->lexer->token['value']);
2686
    }
2687
2688
    /**
2689
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2690
     *
2691
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2692
     */
2693 282
    public function ArithmeticExpression()
2694
    {
2695 282
        $expr = new AST\ArithmeticExpression;
2696
2697 282
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2698 18
            $peek = $this->lexer->glimpse();
2699
2700 18
            if ($peek['type'] === Lexer::T_SELECT) {
2701 6
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2702 6
                $expr->subselect = $this->Subselect();
2703 6
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2704
2705 6
                return $expr;
2706
            }
2707
        }
2708
2709 282
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2710
2711 282
        return $expr;
2712
    }
2713
2714
    /**
2715
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2716
     *
2717
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2718
     */
2719 386
    public function SimpleArithmeticExpression()
2720
    {
2721 386
        $terms   = [];
2722 386
        $terms[] = $this->ArithmeticTerm();
2723
2724 386
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2725 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2726
2727 21
            $terms[] = $this->lexer->token['value'];
2728 21
            $terms[] = $this->ArithmeticTerm();
2729
        }
2730
2731
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2732
        // if only one AST\ArithmeticTerm is defined
2733 386
        if (\count($terms) === 1) {
2734 381
            return $terms[0];
2735
        }
2736
2737 21
        return new AST\SimpleArithmeticExpression($terms);
2738
    }
2739
2740
    /**
2741
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2742
     *
2743
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2744
     */
2745 386
    public function ArithmeticTerm()
2746
    {
2747 386
        $factors   = [];
2748 386
        $factors[] = $this->ArithmeticFactor();
2749
2750 386
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2751 52
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2752
2753 52
            $factors[] = $this->lexer->token['value'];
2754 52
            $factors[] = $this->ArithmeticFactor();
2755
        }
2756
2757
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2758
        // if only one AST\ArithmeticFactor is defined
2759 386
        if (\count($factors) === 1) {
2760 358
            return $factors[0];
2761
        }
2762
2763 52
        return new AST\ArithmeticTerm($factors);
2764
    }
2765
2766
    /**
2767
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2768
     *
2769
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2770
     */
2771 386
    public function ArithmeticFactor()
2772
    {
2773 386
        $sign   = null;
2774 386
        $isPlus = $this->lexer->isNextToken(Lexer::T_PLUS);
2775
2776 386
        if ($isPlus || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2777 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2778 3
            $sign = $isPlus;
2779
        }
2780
2781 386
        $primary = $this->ArithmeticPrimary();
2782
2783
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2784
        // if only one AST\ArithmeticPrimary is defined
2785 386
        if ($sign === null) {
2786 385
            return $primary;
2787
        }
2788
2789 3
        return new AST\ArithmeticFactor($primary, $sign);
2790
    }
2791
2792
    /**
2793
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2794
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2795
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2796
     *          | InputParameter | CaseExpression
2797
     */
2798 399
    public function ArithmeticPrimary()
2799
    {
2800 399
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2801 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2802
2803 25
            $expr = $this->SimpleArithmeticExpression();
2804
2805 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2806
2807 25
            return new AST\ParenthesisExpression($expr);
2808
        }
2809
2810 399
        switch ($this->lexer->lookahead['type']) {
2811
            case Lexer::T_COALESCE:
2812
            case Lexer::T_NULLIF:
2813
            case Lexer::T_CASE:
2814 4
                return $this->CaseExpression();
2815
2816
            case Lexer::T_IDENTIFIER:
2817 373
                $peek = $this->lexer->glimpse();
2818
2819 373
                if ($peek['value'] === '(') {
2820 38
                    return $this->FunctionDeclaration();
2821
                }
2822
2823 346
                if ($peek['value'] === '.') {
2824 339
                    return $this->SingleValuedPathExpression();
2825
                }
2826
2827 42
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2828 10
                    return $this->ResultVariable();
2829
                }
2830
2831 34
                return $this->StateFieldPathExpression();
2832
2833
            case Lexer::T_INPUT_PARAMETER:
2834 116
                return $this->InputParameter();
2835
2836
            default:
2837 163
                $peek = $this->lexer->glimpse();
2838
2839 163
                if ($peek['value'] === '(') {
2840 18
                    return $this->FunctionDeclaration();
2841
                }
2842
2843 159
                return $this->Literal();
2844
        }
2845
    }
2846
2847
    /**
2848
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2849
     *
2850
     * @return \Doctrine\ORM\Query\AST\Subselect |
2851
     *         string
2852
     */
2853 12
    public function StringExpression()
2854
    {
2855 12
        $peek = $this->lexer->glimpse();
2856
2857
        // Subselect
2858 12
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2859
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2860
            $expr = $this->Subselect();
2861
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2862
2863
            return $expr;
2864
        }
2865
2866
        // ResultVariable (string)
2867 12
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2868 12
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2869 2
            return $this->ResultVariable();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->ResultVariable() returns the type string which is incompatible with the documented return type Doctrine\ORM\Query\AST\Subselect.
Loading history...
2870
        }
2871
2872 10
        return $this->StringPrimary();
2873
    }
2874
2875
    /**
2876
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2877
     */
2878 61
    public function StringPrimary()
2879
    {
2880 61
        $lookaheadType = $this->lexer->lookahead['type'];
2881
2882 61
        switch ($lookaheadType) {
2883
            case Lexer::T_IDENTIFIER:
2884 33
                $peek = $this->lexer->glimpse();
2885
2886 33
                if ($peek['value'] === '.') {
2887 33
                    return $this->StateFieldPathExpression();
2888
                }
2889
2890 8
                if ($peek['value'] === '(') {
2891
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2892 8
                    return $this->FunctionDeclaration();
2893
                }
2894
2895
                $this->syntaxError("'.' or '('");
2896
                break;
2897
2898
            case Lexer::T_STRING:
2899 45
                $this->match(Lexer::T_STRING);
2900
2901 45
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2902
2903
            case Lexer::T_INPUT_PARAMETER:
2904 2
                return $this->InputParameter();
2905
2906
            case Lexer::T_CASE:
2907
            case Lexer::T_COALESCE:
2908
            case Lexer::T_NULLIF:
2909
                return $this->CaseExpression();
2910
        }
2911
2912
        $this->syntaxError(
2913
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2914
        );
2915
    }
2916
2917
    /**
2918
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2919
     *
2920
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2921
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2922
     */
2923 8
    public function EntityExpression()
2924
    {
2925 8
        $glimpse = $this->lexer->glimpse();
2926
2927 8
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2928 1
            return $this->SingleValuedAssociationPathExpression();
2929
        }
2930
2931 7
        return $this->SimpleEntityExpression();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->SimpleEntityExpression() returns the type Doctrine\ORM\Query\AST\InputParameter which is incompatible with the documented return type Doctrine\ORM\Query\AST\PathExpression.
Loading history...
2932
    }
2933
2934
    /**
2935
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2936
     *
2937
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2938
     */
2939 7
    public function SimpleEntityExpression()
2940
    {
2941 7
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2942 5
            return $this->InputParameter();
2943
        }
2944
2945 2
        return $this->StateFieldPathExpression();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->StateFieldPathExpression() returns the type Doctrine\ORM\Query\AST\PathExpression which is incompatible with the documented return type string|Doctrine\ORM\Query\AST\InputParameter.
Loading history...
2946
    }
2947
2948
    /**
2949
     * AggregateExpression ::=
2950
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2951
     *
2952
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2953
     */
2954 85
    public function AggregateExpression()
2955
    {
2956 85
        $lookaheadType = $this->lexer->lookahead['type'];
2957 85
        $isDistinct    = false;
2958
2959 85
        if (! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM])) {
2960
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2961
        }
2962
2963 85
        $this->match($lookaheadType);
2964 85
        $functionName = $this->lexer->token['value'];
2965 85
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2966
2967 85
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2968 3
            $this->match(Lexer::T_DISTINCT);
2969 3
            $isDistinct = true;
2970
        }
2971
2972 85
        $pathExp = $this->SimpleArithmeticExpression();
2973
2974 85
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2975
2976 85
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2977
    }
2978
2979
    /**
2980
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2981
     *
2982
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2983
     */
2984 3
    public function QuantifiedExpression()
2985
    {
2986 3
        $lookaheadType = $this->lexer->lookahead['type'];
2987 3
        $value         = $this->lexer->lookahead['value'];
2988
2989 3
        if (! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME])) {
2990
            $this->syntaxError('ALL, ANY or SOME');
2991
        }
2992
2993 3
        $this->match($lookaheadType);
2994 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2995
2996 3
        $qExpr       = new AST\QuantifiedExpression($this->Subselect());
2997 3
        $qExpr->type = $value;
2998
2999 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3000
3001 3
        return $qExpr;
3002
    }
3003
3004
    /**
3005
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
3006
     *
3007
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
3008
     */
3009 5
    public function BetweenExpression()
3010
    {
3011 5
        $not        = false;
3012 5
        $arithExpr1 = $this->ArithmeticExpression();
3013
3014 5
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3015 2
            $this->match(Lexer::T_NOT);
3016 2
            $not = true;
3017
        }
3018
3019 5
        $this->match(Lexer::T_BETWEEN);
3020 5
        $arithExpr2 = $this->ArithmeticExpression();
3021 5
        $this->match(Lexer::T_AND);
3022 5
        $arithExpr3 = $this->ArithmeticExpression();
3023
3024 5
        $betweenExpr      = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3025 5
        $betweenExpr->not = $not;
3026
3027 5
        return $betweenExpr;
3028
    }
3029
3030
    /**
3031
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3032
     *
3033
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3034
     */
3035 257
    public function ComparisonExpression()
3036
    {
3037 257
        $this->lexer->glimpse();
3038
3039 257
        $leftExpr  = $this->ArithmeticExpression();
3040 257
        $operator  = $this->ComparisonOperator();
3041 257
        $rightExpr = ($this->isNextAllAnySome())
3042 3
            ? $this->QuantifiedExpression()
3043 257
            : $this->ArithmeticExpression();
3044
3045 255
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3046
    }
3047
3048
    /**
3049
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3050
     *
3051
     * @return \Doctrine\ORM\Query\AST\InExpression
3052
     */
3053 30
    public function InExpression()
3054
    {
3055 30
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3056
3057 30
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3058 4
            $this->match(Lexer::T_NOT);
3059 4
            $inExpression->not = true;
3060
        }
3061
3062 30
        $this->match(Lexer::T_IN);
3063 30
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3064
3065 30
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3066 9
            $inExpression->subselect = $this->Subselect();
3067
        } else {
3068 21
            $literals   = [];
3069 21
            $literals[] = $this->InParameter();
3070
3071 21
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3072 11
                $this->match(Lexer::T_COMMA);
3073 11
                $literals[] = $this->InParameter();
3074
            }
3075
3076 21
            $inExpression->literals = $literals;
0 ignored issues
show
Documentation Bug introduced by
It seems like $literals of type Doctrine\ORM\Query\AST\InputParameter[] is incompatible with the declared type Doctrine\ORM\Query\AST\Literal[] of property $literals.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
3077
        }
3078
3079 29
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3080
3081 29
        return $inExpression;
3082
    }
3083
3084
    /**
3085
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3086
     *
3087
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3088
     */
3089 17
    public function InstanceOfExpression()
3090
    {
3091 17
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3092
3093 17
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3094 1
            $this->match(Lexer::T_NOT);
3095 1
            $instanceOfExpression->not = true;
3096
        }
3097
3098 17
        $this->match(Lexer::T_INSTANCE);
3099 17
        $this->match(Lexer::T_OF);
3100
3101 17
        $exprValues = [];
3102
3103 17
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3104 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3105
3106 2
            $exprValues[] = $this->InstanceOfParameter();
3107
3108 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3109 2
                $this->match(Lexer::T_COMMA);
3110
3111 2
                $exprValues[] = $this->InstanceOfParameter();
3112
            }
3113
3114 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3115
3116 2
            $instanceOfExpression->value = $exprValues;
3117
3118 2
            return $instanceOfExpression;
3119
        }
3120
3121 15
        $exprValues[] = $this->InstanceOfParameter();
3122
3123 15
        $instanceOfExpression->value = $exprValues;
3124
3125 15
        return $instanceOfExpression;
3126
    }
3127
3128
    /**
3129
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3130
     *
3131
     * @return mixed
3132
     */
3133 17
    public function InstanceOfParameter()
3134
    {
3135 17
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3136 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3137
3138 6
            return new AST\InputParameter($this->lexer->token['value']);
3139
        }
3140
3141 11
        $abstractSchemaName = $this->AbstractSchemaName();
3142
3143 11
        $this->validateAbstractSchemaName($abstractSchemaName);
3144
3145 11
        return $abstractSchemaName;
3146
    }
3147
3148
    /**
3149
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3150
     *
3151
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3152
     */
3153 12
    public function LikeExpression()
3154
    {
3155 12
        $stringExpr = $this->StringExpression();
3156 12
        $not        = false;
3157
3158 12
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3159 2
            $this->match(Lexer::T_NOT);
3160 2
            $not = true;
3161
        }
3162
3163 12
        $this->match(Lexer::T_LIKE);
3164
3165 12
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3166 1
            $this->match(Lexer::T_INPUT_PARAMETER);
3167 1
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3168
        } else {
3169 12
            $stringPattern = $this->StringPrimary();
3170
        }
3171
3172 12
        $escapeChar = null;
3173
3174 12
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3175 1
            $this->match(Lexer::T_ESCAPE);
3176 1
            $this->match(Lexer::T_STRING);
3177
3178 1
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3179
        }
3180
3181 12
        $likeExpr      = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
0 ignored issues
show
Bug introduced by
It seems like $stringPattern can also be of type Doctrine\ORM\Query\AST\Literal and Doctrine\ORM\Query\AST\Functions\FunctionNode and Doctrine\ORM\Query\AST\CoalesceExpression and Doctrine\ORM\Query\AST\GeneralCaseExpression and Doctrine\ORM\Query\AST\PathExpression and Doctrine\ORM\Query\AST\NullIfExpression and Doctrine\ORM\Query\AST\SimpleCaseExpression; however, parameter $stringPattern of Doctrine\ORM\Query\AST\L...pression::__construct() does only seem to accept Doctrine\ORM\Query\AST\InputParameter, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3181
        $likeExpr      = new AST\LikeExpression($stringExpr, /** @scrutinizer ignore-type */ $stringPattern, $escapeChar);
Loading history...
3182 12
        $likeExpr->not = $not;
3183
3184 12
        return $likeExpr;
3185
    }
3186
3187
    /**
3188
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3189
     *
3190
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3191
     */
3192 11
    public function NullComparisonExpression()
3193
    {
3194
        switch (true) {
3195 11
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3196
                $this->match(Lexer::T_INPUT_PARAMETER);
3197
3198
                $expr = new AST\InputParameter($this->lexer->token['value']);
3199
                break;
3200
3201 11
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3202 1
                $expr = $this->NullIfExpression();
3203 1
                break;
3204
3205 11
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3206 1
                $expr = $this->CoalesceExpression();
3207 1
                break;
3208
3209 11
            case $this->isFunction():
3210 2
                $expr = $this->FunctionDeclaration();
3211 2
                break;
3212
3213
            default:
3214
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3215 10
                $glimpse = $this->lexer->glimpse();
3216
3217 10
                if ($glimpse['type'] === Lexer::T_DOT) {
3218 6
                    $expr = $this->SingleValuedPathExpression();
3219
3220
                    // Leave switch statement
3221 6
                    break;
3222
                }
3223
3224 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3225
3226
                // Validate existing component
3227 4
                if (! isset($this->queryComponents[$lookaheadValue])) {
3228
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3229
                }
3230
3231
                // Validate SingleValuedPathExpression (ie.: "product")
3232 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3233 1
                    $expr = $this->SingleValuedPathExpression();
3234 1
                    break;
3235
                }
3236
3237
                // Validating ResultVariable
3238 3
                if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3239
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3240
                }
3241
3242 3
                $expr = $this->ResultVariable();
3243 3
                break;
3244
        }
3245
3246 11
        $nullCompExpr = new AST\NullComparisonExpression($expr);
0 ignored issues
show
Bug introduced by
It seems like $expr can also be of type string; however, parameter $expression of Doctrine\ORM\Query\AST\N...pression::__construct() does only seem to accept Doctrine\ORM\Query\AST\Node, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

3246
        $nullCompExpr = new AST\NullComparisonExpression(/** @scrutinizer ignore-type */ $expr);
Loading history...
3247
3248 11
        $this->match(Lexer::T_IS);
3249
3250 11
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3251 4
            $this->match(Lexer::T_NOT);
3252
3253 4
            $nullCompExpr->not = true;
3254
        }
3255
3256 11
        $this->match(Lexer::T_NULL);
3257
3258 11
        return $nullCompExpr;
3259
    }
3260
3261
    /**
3262
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3263
     *
3264
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
3265
     */
3266 7
    public function ExistsExpression()
3267
    {
3268 7
        $not = false;
3269
3270 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3271
            $this->match(Lexer::T_NOT);
3272
            $not = true;
3273
        }
3274
3275 7
        $this->match(Lexer::T_EXISTS);
3276 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3277
3278 7
        $existsExpression      = new AST\ExistsExpression($this->Subselect());
3279 7
        $existsExpression->not = $not;
3280
3281 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3282
3283 7
        return $existsExpression;
3284
    }
3285
3286
    /**
3287
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3288
     *
3289
     * @return string
3290
     */
3291 257
    public function ComparisonOperator()
3292
    {
3293 257
        switch ($this->lexer->lookahead['value']) {
3294
            case '=':
3295 220
                $this->match(Lexer::T_EQUALS);
3296
3297 220
                return '=';
3298
3299
            case '<':
3300 12
                $this->match(Lexer::T_LOWER_THAN);
3301 12
                $operator = '<';
3302
3303 12
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3304 3
                    $this->match(Lexer::T_EQUALS);
3305 3
                    $operator .= '=';
3306 9
                } elseif ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3307 2
                    $this->match(Lexer::T_GREATER_THAN);
3308 2
                    $operator .= '>';
3309
                }
3310
3311 12
                return $operator;
3312
3313
            case '>':
3314 42
                $this->match(Lexer::T_GREATER_THAN);
3315 42
                $operator = '>';
3316
3317 42
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3318 4
                    $this->match(Lexer::T_EQUALS);
3319 4
                    $operator .= '=';
3320
                }
3321
3322 42
                return $operator;
3323
3324
            case '!':
3325 1
                $this->match(Lexer::T_NEGATE);
3326 1
                $this->match(Lexer::T_EQUALS);
3327
3328 1
                return '<>';
3329
3330
            default:
3331
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
3332
        }
3333
    }
3334
3335
    /**
3336
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3337
     *
3338
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3339
     */
3340 151
    public function FunctionDeclaration()
3341
    {
3342 151
        $token    = $this->lexer->lookahead;
3343 151
        $funcName = strtolower($token['value']);
3344
3345 151
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3346
3347
        // Check for custom functions functions first!
3348 151
        switch (true) {
3349
            case $customFunctionDeclaration !== null:
3350 4
                return $customFunctionDeclaration;
3351
3352 147
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3353 33
                return $this->FunctionsReturningStrings();
3354
3355 119
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3356 104
                return $this->FunctionsReturningNumerics();
3357
3358 16
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3359 16
                return $this->FunctionsReturningDatetime();
3360
3361
            default:
3362
                $this->syntaxError('known function', $token);
3363
        }
3364
    }
3365
3366
    /**
3367
     * Helper function for FunctionDeclaration grammar rule.
3368
     *
3369
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3370
     */
3371 151
    private function CustomFunctionDeclaration()
3372
    {
3373 151
        $token    = $this->lexer->lookahead;
3374 151
        $funcName = strtolower($token['value']);
3375
3376
        // Check for custom functions afterwards
3377 151
        $config = $this->em->getConfiguration();
3378
3379 151
        switch (true) {
3380 151
            case ($config->getCustomStringFunction($funcName) !== null):
3381 3
                return $this->CustomFunctionsReturningStrings();
3382
3383 149
            case ($config->getCustomNumericFunction($funcName) !== null):
3384 2
                return $this->CustomFunctionsReturningNumerics();
3385
3386 147
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3387
                return $this->CustomFunctionsReturningDatetime();
3388
3389
            default:
3390 147
                return null;
3391
        }
3392
    }
3393
3394
    /**
3395
     * FunctionsReturningNumerics ::=
3396
     *      "LENGTH" "(" StringPrimary ")" |
3397
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3398
     *      "ABS" "(" SimpleArithmeticExpression ")" |
3399
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
3400
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3401
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
3402
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3403
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3404
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3405
     *
3406
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3407
     */
3408 104
    public function FunctionsReturningNumerics()
3409
    {
3410 104
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3411 104
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3412
3413 104
        $function = new $funcClass($funcNameLower);
3414 104
        $function->parse($this);
3415
3416 104
        return $function;
3417
    }
3418
3419
    /**
3420
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3421
     */
3422 2
    public function CustomFunctionsReturningNumerics()
3423
    {
3424
        // getCustomNumericFunction is case-insensitive
3425 2
        $functionName  = strtolower($this->lexer->lookahead['value']);
3426 2
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3427
3428 2
        $function = is_string($functionClass)
3429 1
            ? new $functionClass($functionName)
3430 2
            : call_user_func($functionClass, $functionName);
3431
3432 2
        $function->parse($this);
3433
3434 2
        return $function;
3435
    }
3436
3437
    /**
3438
     * FunctionsReturningDateTime ::=
3439
     *     "CURRENT_DATE" |
3440
     *     "CURRENT_TIME" |
3441
     *     "CURRENT_TIMESTAMP" |
3442
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3443
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3444
     *
3445
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3446
     */
3447 16
    public function FunctionsReturningDatetime()
3448
    {
3449 16
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3450 16
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3451
3452 16
        $function = new $funcClass($funcNameLower);
3453 16
        $function->parse($this);
3454
3455 16
        return $function;
3456
    }
3457
3458
    /**
3459
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3460
     */
3461
    public function CustomFunctionsReturningDatetime()
3462
    {
3463
        // getCustomDatetimeFunction is case-insensitive
3464
        $functionName  = $this->lexer->lookahead['value'];
3465
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3466
3467
        $function = is_string($functionClass)
3468
            ? new $functionClass($functionName)
3469
            : call_user_func($functionClass, $functionName);
3470
3471
        $function->parse($this);
3472
3473
        return $function;
3474
    }
3475
3476
    /**
3477
     * FunctionsReturningStrings ::=
3478
     *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3479
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3480
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3481
     *   "LOWER" "(" StringPrimary ")" |
3482
     *   "UPPER" "(" StringPrimary ")" |
3483
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3484
     *
3485
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3486
     */
3487 33
    public function FunctionsReturningStrings()
3488
    {
3489 33
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3490 33
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3491
3492 33
        $function = new $funcClass($funcNameLower);
3493 33
        $function->parse($this);
3494
3495 33
        return $function;
3496
    }
3497
3498
    /**
3499
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3500
     */
3501 3
    public function CustomFunctionsReturningStrings()
3502
    {
3503
        // getCustomStringFunction is case-insensitive
3504 3
        $functionName  = $this->lexer->lookahead['value'];
3505 3
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3506
3507 3
        $function = is_string($functionClass)
3508 2
            ? new $functionClass($functionName)
3509 3
            : call_user_func($functionClass, $functionName);
3510
3511 3
        $function->parse($this);
3512
3513 3
        return $function;
3514
    }
3515
}
3516