Failed Conditions
Push — master ( 8be1e3...e3936d )
by Marco
14s
created

Parser::parse()   C

Complexity

Conditions 9
Paths 20

Size

Total Lines 47
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9.3317

Importance

Changes 0
Metric Value
cc 9
eloc 26
nc 20
nop 0
dl 0
loc 47
ccs 21
cts 25
cp 0.84
crap 9.3317
rs 5.2941
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 848
    public function __construct(Query $query)
169
    {
170 848
        $this->query        = $query;
171 848
        $this->em           = $query->getEntityManager();
172 848
        $this->lexer        = new Lexer($query->getDQL());
173 848
        $this->parserResult = new ParserResult();
174 848
    }
175
176
    /**
177
     * Sets a custom tree walker that produces output.
178
     * This tree walker will be run last over the AST, after any other walkers.
179
     *
180
     * @param string $className
181
     */
182 127
    public function setCustomOutputTreeWalker($className)
183
    {
184 127
        $this->customOutputWalker = $className;
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 127
    }
186
187
    /**
188
     * Adds a custom tree walker for modifying the AST.
189
     *
190
     * @param string $className
191
     */
192
    public function addCustomTreeWalker($className)
193
    {
194
        $this->customTreeWalkers[] = $className;
195
    }
196
197
    /**
198
     * Gets the lexer used by the parser.
199
     *
200
     * @return \Doctrine\ORM\Query\Lexer
201
     */
202 31
    public function getLexer()
203
    {
204 31
        return $this->lexer;
205
    }
206
207
    /**
208
     * Gets the ParserResult that is being filled with information during parsing.
209
     *
210
     * @return \Doctrine\ORM\Query\ParserResult
211
     */
212
    public function getParserResult()
213
    {
214
        return $this->parserResult;
215
    }
216
217
    /**
218
     * Gets the EntityManager used by the parser.
219
     *
220
     * @return \Doctrine\ORM\EntityManagerInterface
221
     */
222 17
    public function getEntityManager()
223
    {
224 17
        return $this->em;
225
    }
226
227
    /**
228
     * Parses and builds AST for the given Query.
229
     *
230
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
231
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
232
     *         \Doctrine\ORM\Query\AST\DeleteStatement
233
     */
234 848
    public function getAST()
235
    {
236
        // Parse & build AST
237 848
        $AST = $this->QueryLanguage();
238
239
        // Process any deferred validations of some nodes in the AST.
240
        // This also allows post-processing of the AST for modification purposes.
241 806
        $this->processDeferredIdentificationVariables();
242
243 804
        if ($this->deferredPartialObjectExpressions) {
244 9
            $this->processDeferredPartialObjectExpressions();
245
        }
246
247 803
        if ($this->deferredPathExpressions) {
248 596
            $this->processDeferredPathExpressions();
249
        }
250
251 801
        if ($this->deferredResultVariables) {
252 32
            $this->processDeferredResultVariables();
253
        }
254
255 801
        if ($this->deferredNewObjectExpressions) {
256 28
            $this->processDeferredNewObjectExpressions($AST);
0 ignored issues
show
Bug introduced by
$AST of type Doctrine\ORM\Query\AST\SelectStatement is incompatible with the type Doctrine\ORM\Query\AST\SelectClause expected by parameter $AST of Doctrine\ORM\Query\Parse...dNewObjectExpressions(). ( Ignorable by Annotation )

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

256
            $this->processDeferredNewObjectExpressions(/** @scrutinizer ignore-type */ $AST);
Loading history...
257
        }
258
259 797
        $this->processRootEntityAliasSelected();
260
261
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
262 796
        $this->fixIdentificationVariableOrder($AST);
263
264 796
        return $AST;
265
    }
266
267
    /**
268
     * Attempts to match the given token with the current lookahead token.
269
     *
270
     * If they match, updates the lookahead token; otherwise raises a syntax
271
     * error.
272
     *
273
     * @param int $token The token type.
274
     *
275
     * @throws QueryException If the tokens don't match.
276
     */
277 859
    public function match($token)
278
    {
279 859
        $lookaheadType = $this->lexer->lookahead['type'];
280
281
        // Short-circuit on first condition, usually types match
282 859
        if ($lookaheadType !== $token) {
283
            // If parameter is not identifier (1-99) must be exact match
284 21
            if ($token < Lexer::T_IDENTIFIER) {
285 3
                $this->syntaxError($this->lexer->getLiteral($token));
286
            }
287
288
            // If parameter is keyword (200+) must be exact match
289 18
            if ($token > Lexer::T_IDENTIFIER) {
290 7
                $this->syntaxError($this->lexer->getLiteral($token));
291
            }
292
293
            // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
294 11
            if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
295 8
                $this->syntaxError($this->lexer->getLiteral($token));
296
            }
297
        }
298
299 852
        $this->lexer->moveNext();
300 852
    }
301
302
    /**
303
     * Frees this parser, enabling it to be reused.
304
     *
305
     * @param bool $deep     Whether to clean peek and reset errors.
306
     * @param int  $position Position to reset.
307
     */
308
    public function free($deep = false, $position = 0)
309
    {
310
        // WARNING! Use this method with care. It resets the scanner!
311
        $this->lexer->resetPosition($position);
312
313
        // Deep = true cleans peek and also any previously defined errors
314
        if ($deep) {
315
            $this->lexer->resetPeek();
316
        }
317
318
        $this->lexer->token     = null;
319
        $this->lexer->lookahead = null;
320
    }
321
322
    /**
323
     * Parses a query string.
324
     *
325
     * @return ParserResult
326
     */
327 848
    public function parse()
328
    {
329 848
        $AST = $this->getAST();
330
331 796
        $customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS);
332 796
        if ($customWalkers !== false) {
333 96
            $this->customTreeWalkers = $customWalkers;
334
        }
335
336 796
        $customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER);
337
338 796
        if ($customOutputWalker !== false) {
339 79
            $this->customOutputWalker = $customOutputWalker;
340
        }
341
342
        // Run any custom tree walkers over the AST
343 796
        if ($this->customTreeWalkers) {
344 95
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
345
346 95
            foreach ($this->customTreeWalkers as $walker) {
347 95
                $treeWalkerChain->addTreeWalker($walker);
348
            }
349
350
            switch (true) {
351 95
                case ($AST instanceof AST\UpdateStatement):
352
                    $treeWalkerChain->walkUpdateStatement($AST);
353
                    break;
354
355 95
                case ($AST instanceof AST\DeleteStatement):
356
                    $treeWalkerChain->walkDeleteStatement($AST);
357
                    break;
358
359 95
                case ($AST instanceof AST\SelectStatement):
360
                default:
361 95
                    $treeWalkerChain->walkSelectStatement($AST);
362
            }
363
364 89
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
365
        }
366
367 790
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
368 790
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
369
370
        // Assign an SQL executor to the parser result
371 790
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
372
373 782
        return $this->parserResult;
374
    }
375
376
    /**
377
     * Fixes order of identification variables.
378
     *
379
     * They have to appear in the select clause in the same order as the
380
     * declarations (from ... x join ... y join ... z ...) appear in the query
381
     * as the hydration process relies on that order for proper operation.
382
     *
383
     * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
384
     */
385 796
    private function fixIdentificationVariableOrder($AST)
386
    {
387 796
        if (count($this->identVariableExpressions) <= 1) {
388 621
            return;
389
        }
390
391 180
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
392 180
            if (! isset($this->identVariableExpressions[$dqlAlias])) {
393 8
                continue;
394
            }
395
396 180
            $expr = $this->identVariableExpressions[$dqlAlias];
397 180
            $key  = array_search($expr, $AST->selectClause->selectExpressions);
0 ignored issues
show
Bug introduced by
The property selectClause does not seem to exist on Doctrine\ORM\Query\AST\DeleteStatement.
Loading history...
Bug introduced by
The property selectClause does not seem to exist on Doctrine\ORM\Query\AST\UpdateStatement.
Loading history...
398
399 180
            unset($AST->selectClause->selectExpressions[$key]);
400
401 180
            $AST->selectClause->selectExpressions[] = $expr;
402
        }
403 180
    }
404
405
    /**
406
     * Generates a new syntax error.
407
     *
408
     * @param string       $expected Expected string.
409
     * @param mixed[]|null $token    Got token.
410
     *
411
     * @throws \Doctrine\ORM\Query\QueryException
412
     */
413 18
    public function syntaxError($expected = '', $token = null)
414
    {
415 18
        if ($token === null) {
416 15
            $token = $this->lexer->lookahead;
417
        }
418
419 18
        $tokenPos = $token['position'] ?? '-1';
420
421 18
        $message  = sprintf('line 0, col %d: Error: ', $tokenPos);
422 18
        $message .= ($expected !== '') ? sprintf('Expected %s, got ', $expected) : 'Unexpected ';
423 18
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : sprintf("'%s'", $token['value']);
424
425 18
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
426
    }
427
428
    /**
429
     * Generates a new semantical error.
430
     *
431
     * @param string       $message Optional message.
432
     * @param mixed[]|null $token   Optional token.
433
     *
434
     * @throws \Doctrine\ORM\Query\QueryException
435
     */
436 33
    public function semanticalError($message = '', $token = null, ?\Throwable $previousFailure = null)
437
    {
438 33
        if ($token === null) {
439 2
            $token = $this->lexer->lookahead;
440
        }
441
442
        // Minimum exposed chars ahead of token
443 33
        $distance = 12;
444
445
        // Find a position of a final word to display in error string
446 33
        $dql    = $this->query->getDQL();
447 33
        $length = strlen($dql);
448 33
        $pos    = $token['position'] + $distance;
449 33
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
450 33
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
451
452 33
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
453 33
        $tokenStr = substr($dql, (int) $token['position'], $length);
454
455
        // Building informative message
456 33
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
457
458 33
        throw QueryException::semanticalError(
459 33
            $message,
460 33
            QueryException::dqlError($this->query->getDQL(), $previousFailure)
461
        );
462
    }
463
464
    /**
465
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
466
     *
467
     * @param bool $resetPeek Reset peek after finding the closing parenthesis.
468
     *
469
     * @return mixed[]
470
     */
471 171
    private function peekBeyondClosingParenthesis($resetPeek = true)
472
    {
473 171
        $token        = $this->lexer->peek();
474 171
        $numUnmatched = 1;
475
476 171
        while ($numUnmatched > 0 && $token !== null) {
477 170
            switch ($token['type']) {
478
                case Lexer::T_OPEN_PARENTHESIS:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
487
                    // Do nothing
488
            }
489
490 170
            $token = $this->lexer->peek();
491
        }
492
493 171
        if ($resetPeek) {
494 150
            $this->lexer->resetPeek();
495
        }
496
497 171
        return $token;
498
    }
499
500
    /**
501
     * Checks if the given token indicates a mathematical operator.
502
     *
503
     * @param mixed[] $token
504
     *
505
     * @return bool TRUE if the token is a mathematical operator, FALSE otherwise.
506
     */
507 359
    private function isMathOperator($token)
508
    {
509 359
        return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
510
    }
511
512
    /**
513
     * Checks if the next-next (after lookahead) token starts a function.
514
     *
515
     * @return bool TRUE if the next-next tokens start a function, FALSE otherwise.
516
     */
517 402
    private function isFunction()
518
    {
519 402
        $lookaheadType = $this->lexer->lookahead['type'];
520 402
        $peek          = $this->lexer->peek();
521
522 402
        $this->lexer->resetPeek();
523
524 402
        return $lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS;
525
    }
526
527
    /**
528
     * Checks whether the given token type indicates an aggregate function.
529
     *
530
     * @param int $tokenType
531
     *
532
     * @return bool TRUE if the token type is an aggregate function, FALSE otherwise.
533
     */
534 1
    private function isAggregateFunction($tokenType)
535
    {
536 1
        return in_array($tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT]);
537
    }
538
539
    /**
540
     * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
541
     *
542
     * @return bool
543
     */
544 301
    private function isNextAllAnySome()
545
    {
546 301
        return in_array($this->lexer->lookahead['type'], [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME]);
547
    }
548
549
    /**
550
     * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
551
     * It must exist in query components list.
552
     */
553 806
    private function processDeferredIdentificationVariables()
554
    {
555 806
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
556 785
            $identVariable = $deferredItem['expression'];
557
558
            // Check if IdentificationVariable exists in queryComponents
559 785
            if (! isset($this->queryComponents[$identVariable])) {
560 1
                $this->semanticalError(
561 1
                    sprintf("'%s' is not defined.", $identVariable),
562 1
                    $deferredItem['token']
563
                );
564
            }
565
566 785
            $qComp = $this->queryComponents[$identVariable];
567
568
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
569 785
            if (! isset($qComp['metadata'])) {
570
                $this->semanticalError(
571
                    sprintf("'%s' does not point to a Class.", $identVariable),
572
                    $deferredItem['token']
573
                );
574
            }
575
576
            // Validate if identification variable nesting level is lower or equal than the current one
577 785
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
578 1
                $this->semanticalError(
579 1
                    sprintf("'%s' is used outside the scope of its declaration.", $identVariable),
580 785
                    $deferredItem['token']
581
                );
582
            }
583
        }
584 804
    }
585
586
    /**
587
     * Validates that the given <tt>NewObjectExpression</tt>.
588
     *
589
     * @param \Doctrine\ORM\Query\AST\SelectClause $AST
590
     */
591 28
    private function processDeferredNewObjectExpressions($AST)
592
    {
593 28
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
594 28
            $expression    = $deferredItem['expression'];
595 28
            $token         = $deferredItem['token'];
596 28
            $className     = $expression->className;
597 28
            $args          = $expression->args;
598 28
            $fromClassName = $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName ?? null;
0 ignored issues
show
Bug introduced by
The property fromClause does not seem to exist on Doctrine\ORM\Query\AST\SelectClause.
Loading history...
599
600
            // If the namespace is not given then assumes the first FROM entity namespace
601 28
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
602 11
                $namespace = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
603 11
                $fqcn      = $namespace . '\\' . $className;
604
605 11
                if (class_exists($fqcn)) {
606 11
                    $expression->className = $fqcn;
607 11
                    $className             = $fqcn;
608
                }
609
            }
610
611 28
            if (! class_exists($className)) {
612 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
613
            }
614
615 27
            $class = new \ReflectionClass($className);
616
617 27
            if (! $class->isInstantiable()) {
618 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
619
            }
620
621 26
            if ($class->getConstructor() === null) {
622 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
623
            }
624
625 25
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
626 25
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
627
            }
628
        }
629 24
    }
630
631
    /**
632
     * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
633
     * It must exist in query components list.
634
     */
635 9
    private function processDeferredPartialObjectExpressions()
636
    {
637 9
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
638 9
            $expr  = $deferredItem['expression'];
639 9
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
640
641 9
            foreach ($expr->partialFieldSet as $field) {
642 9
                $property = $class->getProperty($field);
643
644 9
                if ($property instanceof FieldMetadata ||
645 9
                    ($property instanceof ToOneAssociationMetadata && $property->isOwningSide())) {
646 9
                    continue;
647
                }
648
649
                $this->semanticalError(
650
                    sprintf("There is no mapped field named '%s' on class %s.", $field, $class->getClassName()),
651
                    $deferredItem['token']
652
                );
653
            }
654
655 9
            if (array_intersect($class->identifier, $expr->partialFieldSet) !== $class->identifier) {
656 1
                $this->semanticalError(
657 1
                    sprintf('The partial field selection of class %s must contain the identifier.', $class->getClassName()),
658 9
                    $deferredItem['token']
659
                );
660
            }
661
        }
662 8
    }
663
664
    /**
665
     * Validates that the given <tt>ResultVariable</tt> is semantically correct.
666
     * It must exist in query components list.
667
     */
668 32
    private function processDeferredResultVariables()
669
    {
670 32
        foreach ($this->deferredResultVariables as $deferredItem) {
671 32
            $resultVariable = $deferredItem['expression'];
672
673
            // Check if ResultVariable exists in queryComponents
674 32
            if (! isset($this->queryComponents[$resultVariable])) {
675
                $this->semanticalError(
676
                    sprintf("'%s' is not defined.", $resultVariable),
677
                    $deferredItem['token']
678
                );
679
            }
680
681 32
            $qComp = $this->queryComponents[$resultVariable];
682
683
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
684 32
            if (! isset($qComp['resultVariable'])) {
685
                $this->semanticalError(
686
                    sprintf("'%s' does not point to a ResultVariable.", $resultVariable),
687
                    $deferredItem['token']
688
                );
689
            }
690
691
            // Validate if identification variable nesting level is lower or equal than the current one
692 32
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
693
                $this->semanticalError(
694
                    sprintf("'%s' is used outside the scope of its declaration.", $resultVariable),
695 32
                    $deferredItem['token']
696
                );
697
            }
698
        }
699 32
    }
700
701
    /**
702
     * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
703
     *
704
     * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
705
     * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
706
     * StateFieldPathExpression              ::= IdentificationVariable "." StateField
707
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
708
     * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
709
     */
710 596
    private function processDeferredPathExpressions()
711
    {
712 596
        foreach ($this->deferredPathExpressions as $deferredItem) {
713 596
            $pathExpression = $deferredItem['expression'];
714
715 596
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
716 596
            $class = $qComp['metadata'];
717 596
            $field = $pathExpression->field;
718
719 596
            if ($field === null) {
720 40
                $field = $pathExpression->field = $class->identifier[0];
721
            }
722
723 596
            $property = $class->getProperty($field);
724
725
            // Check if field or association exists
726 596
            if (! $property) {
727
                $this->semanticalError(
728
                    'Class ' . $class->getClassName() . ' has no field or association named ' . $field,
729
                    $deferredItem['token']
730
                );
731
            }
732
733 596
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
734
735 596
            if ($property instanceof AssociationMetadata) {
736 89
                $fieldType = $property instanceof ToOneAssociationMetadata
737 66
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
738 89
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
739
                ;
740
            }
741
742
            // Validate if PathExpression is one of the expected types
743 596
            $expectedType = $pathExpression->expectedType;
744
745 596
            if (! ($expectedType & $fieldType)) {
746
                // We need to recognize which was expected type(s)
747 2
                $expectedStringTypes = [];
748
749
                // Validate state field type
750 2
                if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
751 1
                    $expectedStringTypes[] = 'StateFieldPathExpression';
752
                }
753
754
                // Validate single valued association (*-to-one)
755 2
                if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
756 2
                    $expectedStringTypes[] = 'SingleValuedAssociationField';
757
                }
758
759
                // Validate single valued association (*-to-many)
760 2
                if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
761
                    $expectedStringTypes[] = 'CollectionValuedAssociationField';
762
                }
763
764
                // Build the error message
765 2
                $semanticalError  = 'Invalid PathExpression. ';
766 2
                $semanticalError .= \count($expectedStringTypes) === 1
767 1
                    ? 'Must be a ' . $expectedStringTypes[0] . '.'
768 2
                    : implode(' or ', $expectedStringTypes) . ' expected.';
769
770 2
                $this->semanticalError($semanticalError, $deferredItem['token']);
771
            }
772
773
            // We need to force the type in PathExpression
774 594
            $pathExpression->type = $fieldType;
775
        }
776 594
    }
777
778 797
    private function processRootEntityAliasSelected()
779
    {
780 797
        if (! $this->identVariableExpressions) {
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...
781 236
            return;
782
        }
783
784 571
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
785 571
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
786 571
                return;
787
            }
788
        }
789
790 1
        $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
791
    }
792
793
    /**
794
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
795
     *
796
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
797
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
798
     *         \Doctrine\ORM\Query\AST\DeleteStatement
799
     */
800 848
    public function QueryLanguage()
801
    {
802 848
        $statement = null;
803
804 848
        $this->lexer->moveNext();
805
806 848
        switch ($this->lexer->lookahead['type']) {
807
            case Lexer::T_SELECT:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

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

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

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

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

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

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

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

3177
        $likeExpr      = new AST\LikeExpression($stringExpr, /** @scrutinizer ignore-type */ $stringPattern, $escapeChar);
Loading history...
3178 14
        $likeExpr->not = $not;
3179
3180 14
        return $likeExpr;
3181
    }
3182
3183
    /**
3184
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3185
     *
3186
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3187
     */
3188 13
    public function NullComparisonExpression()
3189
    {
3190
        switch (true) {
3191 13
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3192
                $this->match(Lexer::T_INPUT_PARAMETER);
3193
3194
                $expr = new AST\InputParameter($this->lexer->token['value']);
3195
                break;
3196
3197 13
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3198 1
                $expr = $this->NullIfExpression();
3199 1
                break;
3200
3201 13
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3202 1
                $expr = $this->CoalesceExpression();
3203 1
                break;
3204
3205 13
            case $this->isFunction():
3206 2
                $expr = $this->FunctionDeclaration();
3207 2
                break;
3208
3209
            default:
3210
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3211 12
                $glimpse = $this->lexer->glimpse();
3212
3213 12
                if ($glimpse['type'] === Lexer::T_DOT) {
3214 8
                    $expr = $this->SingleValuedPathExpression();
3215
3216
                    // Leave switch statement
3217 8
                    break;
3218
                }
3219
3220 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3221
3222
                // Validate existing component
3223 4
                if (! isset($this->queryComponents[$lookaheadValue])) {
3224
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3225
                }
3226
3227
                // Validate SingleValuedPathExpression (ie.: "product")
3228 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3229 1
                    $expr = $this->SingleValuedPathExpression();
3230 1
                    break;
3231
                }
3232
3233
                // Validating ResultVariable
3234 3
                if (! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3235
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3236
                }
3237
3238 3
                $expr = $this->ResultVariable();
3239 3
                break;
3240
        }
3241
3242 13
        $nullCompExpr = new AST\NullComparisonExpression($expr);
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

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