Failed Conditions
Pull Request — develop (#6935)
by Michael
167:08 queued 149:28
created

Parser::Join()   C

Complexity

Conditions 7
Paths 32

Size

Total Lines 47
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7.0322

Importance

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

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

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

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

Loading history...
2
3
declare(strict_types=1);
4
5
namespace Doctrine\ORM\Query;
6
7
use Doctrine\Common\Persistence\Mapping\MappingException;
8
use Doctrine\ORM\Mapping\AssociationMetadata;
9
use Doctrine\ORM\Mapping\FieldMetadata;
10
use Doctrine\ORM\Mapping\ToOneAssociationMetadata;
11
use Doctrine\ORM\Query;
12
use Doctrine\ORM\Query\AST\Functions;
13
14
/**
15
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
16
 * Parses a DQL query, reports any errors in it, and generates an AST.
17
 *
18
 * @since   2.0
19
 * @author  Guilherme Blanco <[email protected]>
20
 * @author  Jonathan Wage <[email protected]>
21
 * @author  Roman Borschel <[email protected]>
22
 * @author  Janne Vanhala <[email protected]>
23
 * @author  Fabio B. Silva <[email protected]>
24
 */
25
class Parser
0 ignored issues
show
Bug introduced by
Possible parse error: class missing opening or closing brace
Loading history...
26
{
27
    /**
28
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
29
     *
30
     * @var array
31
     */
32
    private static $_STRING_FUNCTIONS = [
33
        'concat'    => Functions\ConcatFunction::class,
34
        'substring' => Functions\SubstringFunction::class,
35
        'trim'      => Functions\TrimFunction::class,
36
        'lower'     => Functions\LowerFunction::class,
37
        'upper'     => Functions\UpperFunction::class,
38
        'identity'  => Functions\IdentityFunction::class,
39
    ];
40
41
    /**
42
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
43
     *
44
     * @var array
45
     */
46
    private static $_NUMERIC_FUNCTIONS = [
47
        'length'    => Functions\LengthFunction::class,
48
        'locate'    => Functions\LocateFunction::class,
49
        'abs'       => Functions\AbsFunction::class,
50
        'sqrt'      => Functions\SqrtFunction::class,
51
        'mod'       => Functions\ModFunction::class,
52
        'size'      => Functions\SizeFunction::class,
53
        'date_diff' => Functions\DateDiffFunction::class,
54
        'bit_and'   => Functions\BitAndFunction::class,
55
        'bit_or'    => Functions\BitOrFunction::class,
56
57
        // Aggregate functions
58
        'min'       => Functions\MinFunction::class,
59
        'max'       => Functions\MaxFunction::class,
60
        'avg'       => Functions\AvgFunction::class,
61
        'sum'       => Functions\SumFunction::class,
62
        'count'     => Functions\CountFunction::class,
63
    ];
64
65
    /**
66
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
67
     *
68
     * @var array
69
     */
70
    private static $_DATETIME_FUNCTIONS = [
71
        'current_date'      => Functions\CurrentDateFunction::class,
72
        'current_time'      => Functions\CurrentTimeFunction::class,
73
        'current_timestamp' => Functions\CurrentTimestampFunction::class,
74
        'date_add'          => Functions\DateAddFunction::class,
75
        'date_sub'          => Functions\DateSubFunction::class,
76
    ];
77
78
    /*
79
     * Expressions that were encountered during parsing of identifiers and expressions
80
     * and still need to be validated.
81
     */
82
83
    /**
84
     * @var array
85
     */
86
    private $deferredIdentificationVariables = [];
87
88
    /**
89
     * @var array
90
     */
91
    private $deferredPartialObjectExpressions = [];
92
93
    /**
94
     * @var array
95
     */
96
    private $deferredPathExpressions = [];
97
98
    /**
99
     * @var array
100
     */
101
    private $deferredResultVariables = [];
102
103
    /**
104
     * @var array
105
     */
106
    private $deferredNewObjectExpressions = [];
107
108
    /**
109
     * The lexer.
110
     *
111
     * @var \Doctrine\ORM\Query\Lexer
112
     */
113
    private $lexer;
114
115
    /**
116
     * The parser result.
117
     *
118
     * @var \Doctrine\ORM\Query\ParserResult
119
     */
120
    private $parserResult;
121
122
    /**
123
     * The EntityManager.
124
     *
125
     * @var \Doctrine\ORM\EntityManagerInterface
126
     */
127
    private $em;
128
129
    /**
130
     * The Query to parse.
131
     *
132
     * @var Query
133
     */
134
    private $query;
135
136
    /**
137
     * Map of declared query components in the parsed query.
138
     *
139
     * @var array
140
     */
141
    private $queryComponents = [];
142
143
    /**
144
     * Keeps the nesting level of defined ResultVariables.
145
     *
146
     * @var integer
147
     */
148
    private $nestingLevel = 0;
149
150
    /**
151
     * Any additional custom tree walkers that modify the AST.
152
     *
153
     * @var array
154
     */
155
    private $customTreeWalkers = [];
156
157
    /**
158
     * The custom last tree walker, if any, that is responsible for producing the output.
159
     *
160
     * @var TreeWalker
161
     */
162
    private $customOutputWalker;
163
164
    /**
165
     * @var array
166
     */
167
    private $identVariableExpressions = [];
168
169
    /**
170
     * Creates a new query parser object.
171
     *
172
     * @param Query $query The Query to parse.
173
     */
174 838
    public function __construct(Query $query)
175
    {
176 838
        $this->query        = $query;
177 838
        $this->em           = $query->getEntityManager();
178 838
        $this->lexer        = new Lexer($query->getDQL());
179 838
        $this->parserResult = new ParserResult();
180 838
    }
181
182
    /**
183
     * Sets a custom tree walker that produces output.
184
     * This tree walker will be run last over the AST, after any other walkers.
185
     *
186
     * @param string $className
187
     *
188
     * @return void
189
     */
190 120
    public function setCustomOutputTreeWalker($className)
191
    {
192 120
        $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...
193 120
    }
194
195
    /**
196
     * Adds a custom tree walker for modifying the AST.
197
     *
198
     * @param string $className
199
     *
200
     * @return void
201
     */
202
    public function addCustomTreeWalker($className)
203
    {
204
        $this->customTreeWalkers[] = $className;
205
    }
206
207
    /**
208
     * Gets the lexer used by the parser.
209
     *
210
     * @return \Doctrine\ORM\Query\Lexer
211
     */
212 31
    public function getLexer()
213
    {
214 31
        return $this->lexer;
215
    }
216
217
    /**
218
     * Gets the ParserResult that is being filled with information during parsing.
219
     *
220
     * @return \Doctrine\ORM\Query\ParserResult
221
     */
222
    public function getParserResult()
223
    {
224
        return $this->parserResult;
225
    }
226
227
    /**
228
     * Gets the EntityManager used by the parser.
229
     *
230
     * @return \Doctrine\ORM\EntityManagerInterface
231
     */
232 10
    public function getEntityManager()
233
    {
234 10
        return $this->em;
235
    }
236
237
    /**
238
     * Parses and builds AST for the given Query.
239
     *
240
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
241
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
242
     *         \Doctrine\ORM\Query\AST\DeleteStatement
243
     */
244 838
    public function getAST()
245
    {
246
        // Parse & build AST
247 838
        $AST = $this->QueryLanguage();
248
249
        // Process any deferred validations of some nodes in the AST.
250
        // This also allows post-processing of the AST for modification purposes.
251 803
        $this->processDeferredIdentificationVariables();
252
253 801
        if ($this->deferredPartialObjectExpressions) {
254 9
            $this->processDeferredPartialObjectExpressions();
255
        }
256
257 800
        if ($this->deferredPathExpressions) {
258 593
            $this->processDeferredPathExpressions();
259
        }
260
261 798
        if ($this->deferredResultVariables) {
262 32
            $this->processDeferredResultVariables();
263
        }
264
265 798
        if ($this->deferredNewObjectExpressions) {
266 26
            $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

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

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

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

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

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

Loading history...
496 42
                    ++$numUnmatched;
497 42
                    break;
498
499
                case Lexer::T_CLOSE_PARENTHESIS:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

Loading history...
500 170
                    --$numUnmatched;
501 170
                    break;
502
503
                default:
0 ignored issues
show
Coding Style introduced by
DEFAULT statements must be defined using a colon

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

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

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

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

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

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

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

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

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

Loading history...
830 774
                $statement = $this->SelectStatement();
831 743
                break;
832
833
            case Lexer::T_UPDATE:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

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

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

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

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

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

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

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

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