Failed Conditions
Pull Request — 2.6 (#7785)
by Michele
07:10
created

Parser::ArithmeticPrimary()   C

Complexity

Conditions 14
Paths 11

Size

Total Lines 46
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 14

Importance

Changes 0
Metric Value
cc 14
eloc 26
c 0
b 0
f 0
nc 11
nop 0
dl 0
loc 46
ccs 26
cts 26
cp 1
crap 14
rs 6.2666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\Query\AST\Functions;
25
use function strpos;
26
27
/**
28
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
29
 * Parses a DQL query, reports any errors in it, and generates an AST.
30
 *
31
 * @since   2.0
32
 * @author  Guilherme Blanco <[email protected]>
33
 * @author  Jonathan Wage <[email protected]>
34
 * @author  Roman Borschel <[email protected]>
35
 * @author  Janne Vanhala <[email protected]>
36
 * @author  Fabio B. Silva <[email protected]>
37
 */
38
class Parser
39
{
40
    /**
41
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
42
     *
43
     * @var array
44
     */
45
    private static $_STRING_FUNCTIONS = [
46
        'concat'    => Functions\ConcatFunction::class,
47
        'substring' => Functions\SubstringFunction::class,
48
        'trim'      => Functions\TrimFunction::class,
49
        'lower'     => Functions\LowerFunction::class,
50
        'upper'     => Functions\UpperFunction::class,
51
        'identity'  => Functions\IdentityFunction::class,
52
    ];
53
54
    /**
55
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
56
     *
57
     * @var array
58
     */
59
    private static $_NUMERIC_FUNCTIONS = [
60
        'length'    => Functions\LengthFunction::class,
61
        'locate'    => Functions\LocateFunction::class,
62
        'abs'       => Functions\AbsFunction::class,
63
        'sqrt'      => Functions\SqrtFunction::class,
64
        'mod'       => Functions\ModFunction::class,
65
        'size'      => Functions\SizeFunction::class,
66
        'date_diff' => Functions\DateDiffFunction::class,
67
        'bit_and'   => Functions\BitAndFunction::class,
68
        'bit_or'    => Functions\BitOrFunction::class,
69
70
        // Aggregate functions
71
        'min'       => Functions\MinFunction::class,
72
        'max'       => Functions\MaxFunction::class,
73
        'avg'       => Functions\AvgFunction::class,
74
        'sum'       => Functions\SumFunction::class,
75
        'count'     => Functions\CountFunction::class,
76
    ];
77
78
    /**
79
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
80
     *
81
     * @var array
82
     */
83
    private static $_DATETIME_FUNCTIONS = [
84
        'current_date'      => Functions\CurrentDateFunction::class,
85
        'current_time'      => Functions\CurrentTimeFunction::class,
86
        'current_timestamp' => Functions\CurrentTimestampFunction::class,
87
        'date_add'          => Functions\DateAddFunction::class,
88
        'date_sub'          => Functions\DateSubFunction::class,
89
    ];
90
91
    /*
92
     * Expressions that were encountered during parsing of identifiers and expressions
93
     * and still need to be validated.
94
     */
95
96
    /**
97
     * @var array
98
     */
99
    private $deferredIdentificationVariables = [];
100
101
    /**
102
     * @var array
103
     */
104
    private $deferredPartialObjectExpressions = [];
105
106
    /**
107
     * @var array
108
     */
109
    private $deferredPathExpressions = [];
110
111
    /**
112
     * @var array
113
     */
114
    private $deferredResultVariables = [];
115
116
    /**
117
     * @var array
118
     */
119
    private $deferredNewObjectExpressions = [];
120
121
    /**
122
     * The lexer.
123
     *
124
     * @var \Doctrine\ORM\Query\Lexer
125
     */
126
    private $lexer;
127
128
    /**
129
     * The parser result.
130
     *
131
     * @var \Doctrine\ORM\Query\ParserResult
132
     */
133
    private $parserResult;
134
135
    /**
136
     * The EntityManager.
137
     *
138
     * @var \Doctrine\ORM\EntityManager
139
     */
140
    private $em;
141
142
    /**
143
     * The Query to parse.
144
     *
145
     * @var Query
146
     */
147
    private $query;
148
149
    /**
150
     * Map of declared query components in the parsed query.
151
     *
152
     * @var array
153
     */
154
    private $queryComponents = [];
155
156
    /**
157
     * Keeps the nesting level of defined ResultVariables.
158
     *
159
     * @var integer
160
     */
161
    private $nestingLevel = 0;
162
163
    /**
164
     * Any additional custom tree walkers that modify the AST.
165
     *
166
     * @var array
167
     */
168
    private $customTreeWalkers = [];
169
170
    /**
171
     * The custom last tree walker, if any, that is responsible for producing the output.
172
     *
173
     * @var TreeWalker
174
     */
175
    private $customOutputWalker;
176
177
    /**
178
     * @var array
179
     */
180
    private $identVariableExpressions = [];
181
182
    /**
183
     * Creates a new query parser object.
184
     *
185
     * @param Query $query The Query to parse.
186
     */
187 867
    public function __construct(Query $query)
188
    {
189 867
        $this->query        = $query;
190 867
        $this->em           = $query->getEntityManager();
191 867
        $this->lexer        = new Lexer($query->getDQL());
192 867
        $this->parserResult = new ParserResult();
193 867
    }
194
195
    /**
196
     * Sets a custom tree walker that produces output.
197
     * This tree walker will be run last over the AST, after any other walkers.
198
     *
199
     * @param string $className
200
     *
201
     * @return void
202
     */
203 128
    public function setCustomOutputTreeWalker($className)
204
    {
205 128
        $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...
206 128
    }
207
208
    /**
209
     * Adds a custom tree walker for modifying the AST.
210
     *
211
     * @param string $className
212
     *
213
     * @return void
214
     */
215
    public function addCustomTreeWalker($className)
216
    {
217
        $this->customTreeWalkers[] = $className;
218
    }
219
220
    /**
221
     * Gets the lexer used by the parser.
222
     *
223
     * @return \Doctrine\ORM\Query\Lexer
224
     */
225 30
    public function getLexer()
226
    {
227 30
        return $this->lexer;
228
    }
229
230
    /**
231
     * Gets the ParserResult that is being filled with information during parsing.
232
     *
233
     * @return \Doctrine\ORM\Query\ParserResult
234
     */
235
    public function getParserResult()
236
    {
237
        return $this->parserResult;
238
    }
239
240
    /**
241
     * Gets the EntityManager used by the parser.
242
     *
243
     * @return \Doctrine\ORM\EntityManager
244
     */
245
    public function getEntityManager()
246
    {
247
        return $this->em;
248
    }
249
250
    /**
251
     * Parses and builds AST for the given Query.
252
     *
253
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
254
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
255
     *         \Doctrine\ORM\Query\AST\DeleteStatement
256
     */
257 867
    public function getAST()
258
    {
259
        // Parse & build AST
260 867
        $AST = $this->QueryLanguage();
261
262
        // Process any deferred validations of some nodes in the AST.
263
        // This also allows post-processing of the AST for modification purposes.
264 825
        $this->processDeferredIdentificationVariables();
265
266 823
        if ($this->deferredPartialObjectExpressions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredPartialObjectExpressions 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...
267 11
            $this->processDeferredPartialObjectExpressions();
268
        }
269
270 821
        if ($this->deferredPathExpressions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredPathExpressions 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...
271 607
            $this->processDeferredPathExpressions();
272
        }
273
274 818
        if ($this->deferredResultVariables) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredResultVariables 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...
275 32
            $this->processDeferredResultVariables();
276
        }
277
278 818
        if ($this->deferredNewObjectExpressions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredNewObjectExpressions 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...
279 28
            $this->processDeferredNewObjectExpressions($AST);
0 ignored issues
show
Bug introduced by
$AST of type Doctrine\ORM\Query\AST\SelectStatement is incompatible with the type Doctrine\ORM\Query\AST\SelectClause expected by parameter $AST of Doctrine\ORM\Query\Parse...dNewObjectExpressions(). ( Ignorable by Annotation )

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

279
            $this->processDeferredNewObjectExpressions(/** @scrutinizer ignore-type */ $AST);
Loading history...
280
        }
281
282 814
        $this->processRootEntityAliasSelected();
283
284
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
285 813
        $this->fixIdentificationVariableOrder($AST);
286
287 813
        return $AST;
288
    }
289
290
    /**
291
     * Attempts to match the given token with the current lookahead token.
292
     *
293
     * If they match, updates the lookahead token; otherwise raises a syntax
294
     * error.
295
     *
296
     * @param int $token The token type.
297
     *
298
     * @return void
299
     *
300
     * @throws QueryException If the tokens don't match.
301
     */
302 878
    public function match($token)
303
    {
304 878
        $lookaheadType = $this->lexer->lookahead['type'];
305
306
        // Short-circuit on first condition, usually types match
307 878
        if ($lookaheadType === $token) {
308 870
            $this->lexer->moveNext();
309 870
            return;
310
        }
311
312
        // If parameter is not identifier (1-99) must be exact match
313 21
        if ($token < Lexer::T_IDENTIFIER) {
314 3
            $this->syntaxError($this->lexer->getLiteral($token));
315
        }
316
317
        // If parameter is keyword (200+) must be exact match
318 18
        if ($token > Lexer::T_IDENTIFIER) {
319 7
            $this->syntaxError($this->lexer->getLiteral($token));
320
        }
321
322
        // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
323 11
        if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
324 8
            $this->syntaxError($this->lexer->getLiteral($token));
325
        }
326
327 3
        $this->lexer->moveNext();
328 3
    }
329
330
    /**
331
     * Frees this parser, enabling it to be reused.
332
     *
333
     * @param boolean $deep     Whether to clean peek and reset errors.
334
     * @param integer $position Position to reset.
335
     *
336
     * @return void
337
     */
338
    public function free($deep = false, $position = 0)
339
    {
340
        // WARNING! Use this method with care. It resets the scanner!
341
        $this->lexer->resetPosition($position);
342
343
        // Deep = true cleans peek and also any previously defined errors
344
        if ($deep) {
345
            $this->lexer->resetPeek();
346
        }
347
348
        $this->lexer->token = null;
349
        $this->lexer->lookahead = null;
350
    }
351
352
    /**
353
     * Parses a query string.
354
     *
355
     * @return ParserResult
356
     */
357 867
    public function parse()
358
    {
359 867
        $AST = $this->getAST();
360
361 813
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
362 99
            $this->customTreeWalkers = $customWalkers;
363
        }
364
365 813
        if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
366 78
            $this->customOutputWalker = $customOutputWalker;
367
        }
368
369
        // Run any custom tree walkers over the AST
370 813
        if ($this->customTreeWalkers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customTreeWalkers 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...
371 98
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
372
373 98
            foreach ($this->customTreeWalkers as $walker) {
374 98
                $treeWalkerChain->addTreeWalker($walker);
375
            }
376
377
            switch (true) {
378 98
                case ($AST instanceof AST\UpdateStatement):
379
                    $treeWalkerChain->walkUpdateStatement($AST);
380
                    break;
381
382 98
                case ($AST instanceof AST\DeleteStatement):
383
                    $treeWalkerChain->walkDeleteStatement($AST);
384
                    break;
385
386 98
                case ($AST instanceof AST\SelectStatement):
387
                default:
388 98
                    $treeWalkerChain->walkSelectStatement($AST);
389
            }
390
391 92
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
392
        }
393
394 807
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
395 807
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
396
397
        // Assign an SQL executor to the parser result
398 807
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
399
400 799
        return $this->parserResult;
401
    }
402
403
    /**
404
     * Fixes order of identification variables.
405
     *
406
     * They have to appear in the select clause in the same order as the
407
     * declarations (from ... x join ... y join ... z ...) appear in the query
408
     * as the hydration process relies on that order for proper operation.
409
     *
410
     * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
411
     *
412
     * @return void
413
     */
414 813
    private function fixIdentificationVariableOrder($AST)
415
    {
416 813
        if (count($this->identVariableExpressions) <= 1) {
417 636
            return;
418
        }
419
420 182
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
421 182
            if ( ! isset($this->identVariableExpressions[$dqlAlias])) {
422 8
                continue;
423
            }
424
425 182
            $expr = $this->identVariableExpressions[$dqlAlias];
426 182
            $key  = array_search($expr, $AST->selectClause->selectExpressions);
0 ignored issues
show
Bug introduced by
The property selectClause does not seem to exist on Doctrine\ORM\Query\AST\DeleteStatement.
Loading history...
Bug introduced by
The property selectClause does not seem to exist on Doctrine\ORM\Query\AST\UpdateStatement.
Loading history...
427
428 182
            unset($AST->selectClause->selectExpressions[$key]);
429
430 182
            $AST->selectClause->selectExpressions[] = $expr;
431
        }
432 182
    }
433
434
    /**
435
     * Generates a new syntax error.
436
     *
437
     * @param string     $expected Expected string.
438
     * @param array|null $token    Got token.
439
     *
440
     * @return void
441
     *
442
     * @throws \Doctrine\ORM\Query\QueryException
443
     */
444 18
    public function syntaxError($expected = '', $token = null)
445
    {
446 18
        if ($token === null) {
447 15
            $token = $this->lexer->lookahead;
448
        }
449
450 18
        $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
451
452 18
        $message  = "line 0, col {$tokenPos}: Error: ";
453 18
        $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected ';
454 18
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'";
455
456 18
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
457
    }
458
459
    /**
460
     * Generates a new semantical error.
461
     *
462
     * @param string     $message Optional message.
463
     * @param array|null $token   Optional token.
464
     *
465
     * @return void
466
     *
467
     * @throws \Doctrine\ORM\Query\QueryException
468
     */
469 35
    public function semanticalError($message = '', $token = null)
470
    {
471 35
        if ($token === null) {
472 2
            $token = $this->lexer->lookahead ?? ['position' => null];
473
        }
474
475
        // Minimum exposed chars ahead of token
476 35
        $distance = 12;
477
478
        // Find a position of a final word to display in error string
479 35
        $dql    = $this->query->getDQL();
480 35
        $length = strlen($dql);
481 35
        $pos    = $token['position'] + $distance;
482 35
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
483 35
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
484
485 35
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
486 35
        $tokenStr = substr($dql, $token['position'], $length);
487
488
        // Building informative message
489 35
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
490
491 35
        throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL()));
492
    }
493
494
    /**
495
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
496
     *
497
     * @param boolean $resetPeek Reset peek after finding the closing parenthesis.
498
     *
499
     * @return array
500
     */
501 174
    private function peekBeyondClosingParenthesis($resetPeek = true)
502
    {
503 174
        $token = $this->lexer->peek();
504 174
        $numUnmatched = 1;
505
506 174
        while ($numUnmatched > 0 && $token !== null) {
507 173
            switch ($token['type']) {
508 173
                case Lexer::T_OPEN_PARENTHESIS:
509 47
                    ++$numUnmatched;
510 47
                    break;
511
512 173
                case Lexer::T_CLOSE_PARENTHESIS:
513 173
                    --$numUnmatched;
514 173
                    break;
515
516
                default:
517
                    // Do nothing
518
            }
519
520 173
            $token = $this->lexer->peek();
521
        }
522
523 174
        if ($resetPeek) {
524 153
            $this->lexer->resetPeek();
525
        }
526
527 174
        return $token;
528
    }
529
530
    /**
531
     * Checks if the given token indicates a mathematical operator.
532
     *
533
     * @param array $token
534
     *
535
     * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise.
536
     */
537 357
    private function isMathOperator($token)
538
    {
539 357
        return $token !== null && in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
540
    }
541
542
    /**
543
     * Checks if the next-next (after lookahead) token starts a function.
544
     *
545
     * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
546
     */
547 408
    private function isFunction()
548
    {
549 408
        $lookaheadType = $this->lexer->lookahead['type'];
550 408
        $peek          = $this->lexer->peek();
551
552 408
        $this->lexer->resetPeek();
553
554 408
        return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek !== null && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
555
    }
556
557
    /**
558
     * Checks whether the given token type indicates an aggregate function.
559
     *
560
     * @param int $tokenType
561
     *
562
     * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
563
     */
564 4
    private function isAggregateFunction($tokenType)
565
    {
566 4
        return in_array($tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT]);
567
    }
568
569
    /**
570
     * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
571
     *
572
     * @return boolean
573
     */
574 307
    private function isNextAllAnySome()
575
    {
576 307
        return in_array($this->lexer->lookahead['type'], [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME]);
577
    }
578
579
    /**
580
     * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
581
     * It must exist in query components list.
582
     *
583
     * @return void
584
     */
585 825
    private function processDeferredIdentificationVariables()
586
    {
587 825
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
588 801
            $identVariable = $deferredItem['expression'];
589
590
            // Check if IdentificationVariable exists in queryComponents
591 801
            if ( ! isset($this->queryComponents[$identVariable])) {
592 1
                $this->semanticalError(
593 1
                    "'$identVariable' is not defined.", $deferredItem['token']
594
                );
595
            }
596
597 801
            $qComp = $this->queryComponents[$identVariable];
598
599
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
600 801
            if ( ! isset($qComp['metadata'])) {
601
                $this->semanticalError(
602
                    "'$identVariable' does not point to a Class.", $deferredItem['token']
603
                );
604
            }
605
606
            // Validate if identification variable nesting level is lower or equal than the current one
607 801
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
608 1
                $this->semanticalError(
609 801
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
610
                );
611
            }
612
        }
613 823
    }
614
615
    /**
616
     * Validates that the given <tt>NewObjectExpression</tt>.
617
     *
618
     * @param \Doctrine\ORM\Query\AST\SelectClause $AST
619
     *
620
     * @return void
621
     */
622 28
    private function processDeferredNewObjectExpressions($AST)
623
    {
624 28
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
625 28
            $expression     = $deferredItem['expression'];
626 28
            $token          = $deferredItem['token'];
627 28
            $className      = $expression->className;
628 28
            $args           = $expression->args;
629 28
            $fromClassName  = isset($AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName)
0 ignored issues
show
Bug introduced by
The property fromClause does not seem to exist on Doctrine\ORM\Query\AST\SelectClause.
Loading history...
630 28
                ? $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName
631 28
                : null;
632
633
            // If the namespace is not given then assumes the first FROM entity namespace
634 28
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
635 11
                $namespace  = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
636 11
                $fqcn       = $namespace . '\\' . $className;
637
638 11
                if (class_exists($fqcn)) {
639 11
                    $expression->className  = $fqcn;
640 11
                    $className              = $fqcn;
641
                }
642
            }
643
644 28
            if ( ! class_exists($className)) {
645 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
646
            }
647
648 27
            $class = new \ReflectionClass($className);
649
650 27
            if ( ! $class->isInstantiable()) {
651 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
652
            }
653
654 26
            if ($class->getConstructor() === null) {
655 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
656
            }
657
658 25
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
659 25
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
660
            }
661
        }
662 24
    }
663
664
    /**
665
     * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
666
     * It must exist in query components list.
667
     *
668
     * @return void
669
     */
670 11
    private function processDeferredPartialObjectExpressions()
671
    {
672 11
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
673 11
            $expr = $deferredItem['expression'];
674 11
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
675
676 11
            foreach ($expr->partialFieldSet as $field) {
677 11
                if (isset($class->fieldMappings[$field])) {
678 10
                    continue;
679
                }
680
681 3
                if (isset($class->associationMappings[$field]) &&
682 3
                    $class->associationMappings[$field]['isOwningSide'] &&
683 3
                    $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) {
684 2
                    continue;
685
                }
686
687 1
                $this->semanticalError(
688 1
                    "There is no mapped field named '$field' on class " . $class->name . ".", $deferredItem['token']
689
                );
690
            }
691
692 10
            if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
693 1
                $this->semanticalError(
694 1
                    "The partial field selection of class " . $class->name . " must contain the identifier.",
695 10
                    $deferredItem['token']
696
                );
697
            }
698
        }
699 9
    }
700
701
    /**
702
     * Validates that the given <tt>ResultVariable</tt> is semantically correct.
703
     * It must exist in query components list.
704
     *
705
     * @return void
706
     */
707 32
    private function processDeferredResultVariables()
708
    {
709 32
        foreach ($this->deferredResultVariables as $deferredItem) {
710 32
            $resultVariable = $deferredItem['expression'];
711
712
            // Check if ResultVariable exists in queryComponents
713 32
            if ( ! isset($this->queryComponents[$resultVariable])) {
714
                $this->semanticalError(
715
                    "'$resultVariable' is not defined.", $deferredItem['token']
716
                );
717
            }
718
719 32
            $qComp = $this->queryComponents[$resultVariable];
720
721
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
722 32
            if ( ! isset($qComp['resultVariable'])) {
723
                $this->semanticalError(
724
                    "'$resultVariable' does not point to a ResultVariable.", $deferredItem['token']
725
                );
726
            }
727
728
            // Validate if identification variable nesting level is lower or equal than the current one
729 32
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
730
                $this->semanticalError(
731 32
                    "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
732
                );
733
            }
734
        }
735 32
    }
736
737
    /**
738
     * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
739
     *
740
     * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
741
     * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
742
     * StateFieldPathExpression              ::= IdentificationVariable "." StateField
743
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
744
     * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
745
     *
746
     * @return void
747
     */
748 607
    private function processDeferredPathExpressions()
749
    {
750 607
        foreach ($this->deferredPathExpressions as $deferredItem) {
751 607
            $pathExpression = $deferredItem['expression'];
752
753 607
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
754 607
            $class = $qComp['metadata'];
755
756 607
            if (($field = $pathExpression->field) === null) {
757 40
                $field = $pathExpression->field = $class->identifier[0];
758
            }
759
760
            // Check if field or association exists
761 607
            if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
762 1
                $this->semanticalError(
763 1
                    'Class ' . $class->name . ' has no field or association named ' . $field,
764 1
                    $deferredItem['token']
765
                );
766
            }
767
768 606
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
769
770 606
            if (isset($class->associationMappings[$field])) {
771 89
                $assoc = $class->associationMappings[$field];
772
773 89
                $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
774 67
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
775 89
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
776
            }
777
778
            // Validate if PathExpression is one of the expected types
779 606
            $expectedType = $pathExpression->expectedType;
780
781 606
            if ( ! ($expectedType & $fieldType)) {
782
                // We need to recognize which was expected type(s)
783 2
                $expectedStringTypes = [];
784
785
                // Validate state field type
786 2
                if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
787 1
                    $expectedStringTypes[] = 'StateFieldPathExpression';
788
                }
789
790
                // Validate single valued association (*-to-one)
791 2
                if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
792 2
                    $expectedStringTypes[] = 'SingleValuedAssociationField';
793
                }
794
795
                // Validate single valued association (*-to-many)
796 2
                if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
797
                    $expectedStringTypes[] = 'CollectionValuedAssociationField';
798
                }
799
800
                // Build the error message
801 2
                $semanticalError  = 'Invalid PathExpression. ';
802 2
                $semanticalError .= (count($expectedStringTypes) == 1)
803 1
                    ? 'Must be a ' . $expectedStringTypes[0] . '.'
804 2
                    : implode(' or ', $expectedStringTypes) . ' expected.';
805
806 2
                $this->semanticalError($semanticalError, $deferredItem['token']);
807
            }
808
809
            // We need to force the type in PathExpression
810 604
            $pathExpression->type = $fieldType;
811
        }
812 604
    }
813
814
    /**
815
     * @return void
816
     */
817 814
    private function processRootEntityAliasSelected()
818
    {
819 814
        if ( ! count($this->identVariableExpressions)) {
820 240
            return;
821
        }
822
823 585
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
824 585
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
825 585
                return;
826
            }
827
        }
828
829 1
        $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
830
    }
831
832
    /**
833
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
834
     *
835
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
836
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
837
     *         \Doctrine\ORM\Query\AST\DeleteStatement
838
     */
839 867
    public function QueryLanguage()
840
    {
841 867
        $statement = null;
842
843 867
        $this->lexer->moveNext();
844
845 867
        switch ($this->lexer->lookahead === null ? null : $this->lexer->lookahead['type']) {
846 867
            case Lexer::T_SELECT:
847 801
                $statement = $this->SelectStatement();
848 763
                break;
849
850 73
            case Lexer::T_UPDATE:
851 32
                $statement = $this->UpdateStatement();
852 32
                break;
853
854 43
            case Lexer::T_DELETE:
855 42
                $statement = $this->DeleteStatement();
856 42
                break;
857
858
            default:
859 2
                $this->syntaxError('SELECT, UPDATE or DELETE');
860
                break;
861
        }
862
863
        // Check for end of string
864 829
        if ($this->lexer->lookahead !== null) {
865 4
            $this->syntaxError('end of string');
866
        }
867
868 825
        return $statement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statement also could return the type Doctrine\ORM\Query\AST\D...ery\AST\UpdateStatement which is incompatible with the documented return type Doctrine\ORM\Query\AST\SelectStatement.
Loading history...
869
    }
870
871
    /**
872
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
873
     *
874
     * @return \Doctrine\ORM\Query\AST\SelectStatement
875
     */
876 801
    public function SelectStatement()
877
    {
878 801
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
879
880 767
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
881 764
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
882 763
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
883 763
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
884
885 763
        return $selectStatement;
886
    }
887
888
    /**
889
     * UpdateStatement ::= UpdateClause [WhereClause]
890
     *
891
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
892
     */
893 32
    public function UpdateStatement()
894
    {
895 32
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
896
897 32
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
898
899 32
        return $updateStatement;
900
    }
901
902
    /**
903
     * DeleteStatement ::= DeleteClause [WhereClause]
904
     *
905
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
906
     */
907 42
    public function DeleteStatement()
908
    {
909 42
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
910
911 42
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
912
913 42
        return $deleteStatement;
914
    }
915
916
    /**
917
     * IdentificationVariable ::= identifier
918
     *
919
     * @return string
920
     */
921 832
    public function IdentificationVariable()
922
    {
923 832
        $this->match(Lexer::T_IDENTIFIER);
924
925 832
        $identVariable = $this->lexer->token['value'];
926
927 832
        $this->deferredIdentificationVariables[] = [
928 832
            'expression'   => $identVariable,
929 832
            'nestingLevel' => $this->nestingLevel,
930 832
            'token'        => $this->lexer->token,
931
        ];
932
933 832
        return $identVariable;
934
    }
935
936
    /**
937
     * AliasIdentificationVariable = identifier
938
     *
939
     * @return string
940
     */
941 835
    public function AliasIdentificationVariable()
942
    {
943 835
        $this->match(Lexer::T_IDENTIFIER);
944
945 835
        $aliasIdentVariable = $this->lexer->token['value'];
946 835
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
947
948 835
        if ($exists) {
949 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
950
        }
951
952 835
        return $aliasIdentVariable;
953
    }
954
955
    /**
956
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
957
     *
958
     * @return string
959
     */
960 857
    public function AbstractSchemaName()
961
    {
962 857
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
963 839
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
964
965 839
            return $this->lexer->token['value'];
966
        }
967
968 29
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
969 19
            $this->match(Lexer::T_IDENTIFIER);
970
971 19
            return $this->lexer->token['value'];
972
        }
973
974 11
        $this->match(Lexer::T_ALIASED_NAME);
975
976 10
        [$namespaceAlias, $simpleClassName] = explode(':', $this->lexer->token['value']);
977
978 10
        return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
979
    }
980
981
    /**
982
     * Validates an AbstractSchemaName, making sure the class exists.
983
     *
984
     * @param string $schemaName The name to validate.
985
     *
986
     * @throws QueryException if the name does not exist.
987
     */
988 851
    private function validateAbstractSchemaName($schemaName)
989
    {
990 851
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
991 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
992
        }
993 836
    }
994
995
    /**
996
     * AliasResultVariable ::= identifier
997
     *
998
     * @return string
999
     */
1000 136
    public function AliasResultVariable()
1001
    {
1002 136
        $this->match(Lexer::T_IDENTIFIER);
1003
1004 132
        $resultVariable = $this->lexer->token['value'];
1005 132
        $exists = isset($this->queryComponents[$resultVariable]);
1006
1007 132
        if ($exists) {
1008 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1009
        }
1010
1011 132
        return $resultVariable;
1012
    }
1013
1014
    /**
1015
     * ResultVariable ::= identifier
1016
     *
1017
     * @return string
1018
     */
1019 32
    public function ResultVariable()
1020
    {
1021 32
        $this->match(Lexer::T_IDENTIFIER);
1022
1023 32
        $resultVariable = $this->lexer->token['value'];
1024
1025
        // Defer ResultVariable validation
1026 32
        $this->deferredResultVariables[] = [
1027 32
            'expression'   => $resultVariable,
1028 32
            'nestingLevel' => $this->nestingLevel,
1029 32
            'token'        => $this->lexer->token,
1030
        ];
1031
1032 32
        return $resultVariable;
1033
    }
1034
1035
    /**
1036
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1037
     *
1038
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1039
     */
1040 259
    public function JoinAssociationPathExpression()
1041
    {
1042 259
        $identVariable = $this->IdentificationVariable();
1043
1044 259
        if ( ! isset($this->queryComponents[$identVariable])) {
1045
            $this->semanticalError(
1046
                'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.'
1047
            );
1048
        }
1049
1050 259
        $this->match(Lexer::T_DOT);
1051 259
        $this->match(Lexer::T_IDENTIFIER);
1052
1053 259
        $field = $this->lexer->token['value'];
1054
1055
        // Validate association field
1056 259
        $qComp = $this->queryComponents[$identVariable];
1057 259
        $class = $qComp['metadata'];
1058
1059 259
        if ( ! $class->hasAssociation($field)) {
1060
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
1061
        }
1062
1063 259
        return new AST\JoinAssociationPathExpression($identVariable, $field);
1064
    }
1065
1066
    /**
1067
     * Parses an arbitrary path expression and defers semantical validation
1068
     * based on expected types.
1069
     *
1070
     * PathExpression ::= IdentificationVariable {"." identifier}*
1071
     *
1072
     * @param integer $expectedTypes
1073
     *
1074
     * @return \Doctrine\ORM\Query\AST\PathExpression
1075
     */
1076 617
    public function PathExpression($expectedTypes)
1077
    {
1078 617
        $identVariable = $this->IdentificationVariable();
1079 617
        $field = null;
1080
1081 617
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1082 610
            $this->match(Lexer::T_DOT);
1083 610
            $this->match(Lexer::T_IDENTIFIER);
1084
1085 610
            $field = $this->lexer->token['value'];
1086
1087 610
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1088 2
                $this->match(Lexer::T_DOT);
1089 2
                $this->match(Lexer::T_IDENTIFIER);
1090 2
                $field .= '.'.$this->lexer->token['value'];
1091
            }
1092
        }
1093
1094
        // Creating AST node
1095 617
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1096
1097
        // Defer PathExpression validation if requested to be deferred
1098 617
        $this->deferredPathExpressions[] = [
1099 617
            'expression'   => $pathExpr,
1100 617
            'nestingLevel' => $this->nestingLevel,
1101 617
            'token'        => $this->lexer->token,
1102
        ];
1103
1104 617
        return $pathExpr;
1105
    }
1106
1107
    /**
1108
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
1109
     *
1110
     * @return \Doctrine\ORM\Query\AST\PathExpression
1111
     */
1112
    public function AssociationPathExpression()
1113
    {
1114
        return $this->PathExpression(
1115
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
1116
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
1117
        );
1118
    }
1119
1120
    /**
1121
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1122
     *
1123
     * @return \Doctrine\ORM\Query\AST\PathExpression
1124
     */
1125 524
    public function SingleValuedPathExpression()
1126
    {
1127 524
        return $this->PathExpression(
1128 524
            AST\PathExpression::TYPE_STATE_FIELD |
1129 524
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1130
        );
1131
    }
1132
1133
    /**
1134
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1135
     *
1136
     * @return \Doctrine\ORM\Query\AST\PathExpression
1137
     */
1138 208
    public function StateFieldPathExpression()
1139
    {
1140 208
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1141
    }
1142
1143
    /**
1144
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1145
     *
1146
     * @return \Doctrine\ORM\Query\AST\PathExpression
1147
     */
1148 9
    public function SingleValuedAssociationPathExpression()
1149
    {
1150 9
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1151
    }
1152
1153
    /**
1154
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1155
     *
1156
     * @return \Doctrine\ORM\Query\AST\PathExpression
1157
     */
1158 22
    public function CollectionValuedPathExpression()
1159
    {
1160 22
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1161
    }
1162
1163
    /**
1164
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1165
     *
1166
     * @return \Doctrine\ORM\Query\AST\SelectClause
1167
     */
1168 801
    public function SelectClause()
1169
    {
1170 801
        $isDistinct = false;
1171 801
        $this->match(Lexer::T_SELECT);
1172
1173
        // Check for DISTINCT
1174 801
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1175 6
            $this->match(Lexer::T_DISTINCT);
1176
1177 6
            $isDistinct = true;
1178
        }
1179
1180
        // Process SelectExpressions (1..N)
1181 801
        $selectExpressions = [];
1182 801
        $selectExpressions[] = $this->SelectExpression();
1183
1184 793
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1185 301
            $this->match(Lexer::T_COMMA);
1186
1187 301
            $selectExpressions[] = $this->SelectExpression();
1188
        }
1189
1190 792
        return new AST\SelectClause($selectExpressions, $isDistinct);
1191
    }
1192
1193
    /**
1194
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1195
     *
1196
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1197
     */
1198 50
    public function SimpleSelectClause()
1199
    {
1200 50
        $isDistinct = false;
1201 50
        $this->match(Lexer::T_SELECT);
1202
1203 50
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1204
            $this->match(Lexer::T_DISTINCT);
1205
1206
            $isDistinct = true;
1207
        }
1208
1209 50
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1210
    }
1211
1212
    /**
1213
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1214
     *
1215
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1216
     */
1217 32
    public function UpdateClause()
1218
    {
1219 32
        $this->match(Lexer::T_UPDATE);
1220
1221 32
        $token = $this->lexer->lookahead;
1222 32
        $abstractSchemaName = $this->AbstractSchemaName();
1223
1224 32
        $this->validateAbstractSchemaName($abstractSchemaName);
1225
1226 32
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1227 2
            $this->match(Lexer::T_AS);
1228
        }
1229
1230 32
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1231
1232 32
        $class = $this->em->getClassMetadata($abstractSchemaName);
1233
1234
        // Building queryComponent
1235
        $queryComponent = [
1236 32
            'metadata'     => $class,
1237
            'parent'       => null,
1238
            'relation'     => null,
1239
            'map'          => null,
1240 32
            'nestingLevel' => $this->nestingLevel,
1241 32
            'token'        => $token,
1242
        ];
1243
1244 32
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1245
1246 32
        $this->match(Lexer::T_SET);
1247
1248 32
        $updateItems = [];
1249 32
        $updateItems[] = $this->UpdateItem();
1250
1251 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1252 5
            $this->match(Lexer::T_COMMA);
1253
1254 5
            $updateItems[] = $this->UpdateItem();
1255
        }
1256
1257 32
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1258 32
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1259
1260 32
        return $updateClause;
1261
    }
1262
1263
    /**
1264
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1265
     *
1266
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1267
     */
1268 42
    public function DeleteClause()
1269
    {
1270 42
        $this->match(Lexer::T_DELETE);
1271
1272 42
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1273 9
            $this->match(Lexer::T_FROM);
1274
        }
1275
1276 42
        $token = $this->lexer->lookahead;
1277 42
        $abstractSchemaName = $this->AbstractSchemaName();
1278
1279 42
        $this->validateAbstractSchemaName($abstractSchemaName);
1280
1281 42
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1282
1283 42
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1284 1
            $this->match(Lexer::T_AS);
1285
        }
1286
1287 42
        $aliasIdentificationVariable = $this->lexer->isNextToken(Lexer::T_IDENTIFIER)
1288 40
            ? $this->AliasIdentificationVariable()
1289 42
            : 'alias_should_have_been_set';
1290
1291 42
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1292 42
        $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1293
1294
        // Building queryComponent
1295
        $queryComponent = [
1296 42
            'metadata'     => $class,
1297
            'parent'       => null,
1298
            'relation'     => null,
1299
            'map'          => null,
1300 42
            'nestingLevel' => $this->nestingLevel,
1301 42
            'token'        => $token,
1302
        ];
1303
1304 42
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1305
1306 42
        return $deleteClause;
1307
    }
1308
1309
    /**
1310
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1311
     *
1312
     * @return \Doctrine\ORM\Query\AST\FromClause
1313
     */
1314 792
    public function FromClause()
1315
    {
1316 792
        $this->match(Lexer::T_FROM);
1317
1318 787
        $identificationVariableDeclarations = [];
1319 787
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1320
1321 767
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1322 6
            $this->match(Lexer::T_COMMA);
1323
1324 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1325
        }
1326
1327 767
        return new AST\FromClause($identificationVariableDeclarations);
1328
    }
1329
1330
    /**
1331
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1332
     *
1333
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1334
     */
1335 50
    public function SubselectFromClause()
1336
    {
1337 50
        $this->match(Lexer::T_FROM);
1338
1339 50
        $identificationVariables = [];
1340 50
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1341
1342 49
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1343
            $this->match(Lexer::T_COMMA);
1344
1345
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1346
        }
1347
1348 49
        return new AST\SubselectFromClause($identificationVariables);
1349
    }
1350
1351
    /**
1352
     * WhereClause ::= "WHERE" ConditionalExpression
1353
     *
1354
     * @return \Doctrine\ORM\Query\AST\WhereClause
1355
     */
1356 350
    public function WhereClause()
1357
    {
1358 350
        $this->match(Lexer::T_WHERE);
1359
1360 350
        return new AST\WhereClause($this->ConditionalExpression());
1361
    }
1362
1363
    /**
1364
     * HavingClause ::= "HAVING" ConditionalExpression
1365
     *
1366
     * @return \Doctrine\ORM\Query\AST\HavingClause
1367
     */
1368 21
    public function HavingClause()
1369
    {
1370 21
        $this->match(Lexer::T_HAVING);
1371
1372 21
        return new AST\HavingClause($this->ConditionalExpression());
1373
    }
1374
1375
    /**
1376
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1377
     *
1378
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1379
     */
1380 36
    public function GroupByClause()
1381
    {
1382 36
        $this->match(Lexer::T_GROUP);
1383 36
        $this->match(Lexer::T_BY);
1384
1385 36
        $groupByItems = [$this->GroupByItem()];
1386
1387 35
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1388 8
            $this->match(Lexer::T_COMMA);
1389
1390 8
            $groupByItems[] = $this->GroupByItem();
1391
        }
1392
1393 35
        return new AST\GroupByClause($groupByItems);
1394
    }
1395
1396
    /**
1397
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1398
     *
1399
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1400
     */
1401 183
    public function OrderByClause()
1402
    {
1403 183
        $this->match(Lexer::T_ORDER);
1404 183
        $this->match(Lexer::T_BY);
1405
1406 183
        $orderByItems = [];
1407 183
        $orderByItems[] = $this->OrderByItem();
1408
1409 183
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1410 15
            $this->match(Lexer::T_COMMA);
1411
1412 15
            $orderByItems[] = $this->OrderByItem();
1413
        }
1414
1415 183
        return new AST\OrderByClause($orderByItems);
1416
    }
1417
1418
    /**
1419
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1420
     *
1421
     * @return \Doctrine\ORM\Query\AST\Subselect
1422
     */
1423 50
    public function Subselect()
1424
    {
1425
        // Increase query nesting level
1426 50
        $this->nestingLevel++;
1427
1428 50
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1429
1430 49
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1431 49
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1432 49
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1433 49
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1434
1435
        // Decrease query nesting level
1436 49
        $this->nestingLevel--;
1437
1438 49
        return $subselect;
1439
    }
1440
1441
    /**
1442
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1443
     *
1444
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1445
     */
1446 32
    public function UpdateItem()
1447
    {
1448 32
        $pathExpr = $this->SingleValuedPathExpression();
1449
1450 32
        $this->match(Lexer::T_EQUALS);
1451
1452 32
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1453
1454 32
        return $updateItem;
1455
    }
1456
1457
    /**
1458
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1459
     *
1460
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1461
     */
1462 36
    public function GroupByItem()
1463
    {
1464
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1465 36
        $glimpse = $this->lexer->glimpse();
1466
1467 36
        if ($glimpse !== null && $glimpse['type'] === Lexer::T_DOT) {
1468 16
            return $this->SingleValuedPathExpression();
1469
        }
1470
1471
        // Still need to decide between IdentificationVariable or ResultVariable
1472 20
        $lookaheadValue = $this->lexer->lookahead['value'];
1473
1474 20
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1475 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1476
        }
1477
1478 19
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1479 17
            ? $this->IdentificationVariable()
1480 19
            : $this->ResultVariable();
1481
    }
1482
1483
    /**
1484
     * OrderByItem ::= (
1485
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1486
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1487
     * ) ["ASC" | "DESC"]
1488
     *
1489
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1490
     */
1491 183
    public function OrderByItem()
1492
    {
1493 183
        $this->lexer->peek(); // lookahead => '.'
1494 183
        $this->lexer->peek(); // lookahead => token after '.'
1495
1496 183
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1497
1498 183
        $this->lexer->resetPeek();
1499
1500 183
        $glimpse = $this->lexer->glimpse();
1501
1502
        switch (true) {
1503 183
            case ($this->isFunction()):
1504 2
                $expr = $this->FunctionDeclaration();
1505 2
                break;
1506
1507 181
            case ($this->isMathOperator($peek)):
1508 25
                $expr = $this->SimpleArithmeticExpression();
1509 25
                break;
1510
1511 157
            case ($glimpse !== null && $glimpse['type'] === Lexer::T_DOT):
1512 142
                $expr = $this->SingleValuedPathExpression();
1513 142
                break;
1514
1515 19
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1516 2
                $expr = $this->ScalarExpression();
1517 2
                break;
1518
1519
            default:
1520 17
                $expr = $this->ResultVariable();
1521 17
                break;
1522
        }
1523
1524 183
        $type = 'ASC';
1525 183
        $item = new AST\OrderByItem($expr);
1526
1527
        switch (true) {
1528 183
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1529 95
                $this->match(Lexer::T_DESC);
1530 95
                $type = 'DESC';
1531 95
                break;
1532
1533 155
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1534 97
                $this->match(Lexer::T_ASC);
1535 97
                break;
1536
1537
            default:
1538
                // Do nothing
1539
        }
1540
1541 183
        $item->type = $type;
1542
1543 183
        return $item;
1544
    }
1545
1546
    /**
1547
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1548
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1549
     *
1550
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1551
     * grammar that needs to be supported:
1552
     *
1553
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1554
     *
1555
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1556
     *
1557
     * @return AST\ArithmeticExpression
1558
     */
1559 32
    public function NewValue()
1560
    {
1561 32
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1562 1
            $this->match(Lexer::T_NULL);
1563
1564 1
            return null;
1565
        }
1566
1567 31
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1568 19
            $this->match(Lexer::T_INPUT_PARAMETER);
1569
1570 19
            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...
1571
        }
1572
1573 12
        return $this->ArithmeticExpression();
1574
    }
1575
1576
    /**
1577
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1578
     *
1579
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1580
     */
1581 789
    public function IdentificationVariableDeclaration()
1582
    {
1583 789
        $joins                    = [];
1584 789
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1585 772
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1586 8
            ? $this->IndexBy()
1587 772
            : null;
1588
1589 772
        $rangeVariableDeclaration->isRoot = true;
1590
1591
        while (
1592 772
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1593 772
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1594 772
            $this->lexer->isNextToken(Lexer::T_JOIN)
1595
        ) {
1596 281
            $joins[] = $this->Join();
1597
        }
1598
1599 769
        return new AST\IdentificationVariableDeclaration(
1600 769
            $rangeVariableDeclaration, $indexBy, $joins
1601
        );
1602
    }
1603
1604
    /**
1605
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1606
     *
1607
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1608
     * Desired EBNF support:
1609
     *
1610
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1611
     *
1612
     * It demands that entire SQL generation to become programmatical. This is
1613
     * needed because association based subselect requires "WHERE" conditional
1614
     * expressions to be injected, but there is no scope to do that. Only scope
1615
     * accessible is "FROM", prohibiting an easy implementation without larger
1616
     * changes.}
1617
     *
1618
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1619
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1620
     */
1621 50
    public function SubselectIdentificationVariableDeclaration()
1622
    {
1623
        /*
1624
        NOT YET IMPLEMENTED!
1625
1626
        $glimpse = $this->lexer->glimpse();
1627
1628
        if ($glimpse['type'] == Lexer::T_DOT) {
1629
            $associationPathExpression = $this->AssociationPathExpression();
1630
1631
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1632
                $this->match(Lexer::T_AS);
1633
            }
1634
1635
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1636
            $identificationVariable      = $associationPathExpression->identificationVariable;
1637
            $field                       = $associationPathExpression->associationField;
1638
1639
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1640
            $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1641
1642
            // Building queryComponent
1643
            $joinQueryComponent = array(
1644
                'metadata'     => $targetClass,
1645
                'parent'       => $identificationVariable,
1646
                'relation'     => $class->getAssociationMapping($field),
1647
                'map'          => null,
1648
                'nestingLevel' => $this->nestingLevel,
1649
                'token'        => $this->lexer->lookahead
1650
            );
1651
1652
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1653
1654
            return new AST\SubselectIdentificationVariableDeclaration(
1655
                $associationPathExpression, $aliasIdentificationVariable
1656
            );
1657
        }
1658
        */
1659
1660 50
        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...
1661
    }
1662
1663
    /**
1664
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1665
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1666
     *          ["WITH" ConditionalExpression]
1667
     *
1668
     * @return \Doctrine\ORM\Query\AST\Join
1669
     */
1670 281
    public function Join()
1671
    {
1672
        // Check Join type
1673 281
        $joinType = AST\Join::JOIN_TYPE_INNER;
1674
1675
        switch (true) {
1676 281
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1677 68
                $this->match(Lexer::T_LEFT);
1678
1679 68
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1680
1681
                // Possible LEFT OUTER join
1682 68
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1683
                    $this->match(Lexer::T_OUTER);
1684
1685
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1686
                }
1687 68
                break;
1688
1689 217
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1690 21
                $this->match(Lexer::T_INNER);
1691 21
                break;
1692
1693
            default:
1694
                // Do nothing
1695
        }
1696
1697 281
        $this->match(Lexer::T_JOIN);
1698
1699 281
        $next            = $this->lexer->glimpse();
1700 281
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1701 278
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1702 278
        $join            = new AST\Join($joinType, $joinDeclaration);
1703
1704
        // Describe non-root join declaration
1705 278
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1706 22
            $joinDeclaration->isRoot = false;
1707
        }
1708
1709
        // Check for ad-hoc Join conditions
1710 278
        if ($adhocConditions) {
1711 24
            $this->match(Lexer::T_WITH);
1712
1713 24
            $join->conditionalExpression = $this->ConditionalExpression();
1714
        }
1715
1716 278
        return $join;
1717
    }
1718
1719
    /**
1720
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1721
     *
1722
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1723
     *
1724
     * @throws QueryException
1725
     */
1726 789
    public function RangeVariableDeclaration()
1727
    {
1728 789
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
1729 2
            $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1730
        }
1731
1732 788
        $abstractSchemaName = $this->AbstractSchemaName();
1733
1734 787
        $this->validateAbstractSchemaName($abstractSchemaName);
1735
1736 772
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1737 6
            $this->match(Lexer::T_AS);
1738
        }
1739
1740 772
        $token = $this->lexer->lookahead;
1741 772
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1742 772
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1743
1744
        // Building queryComponent
1745
        $queryComponent = [
1746 772
            'metadata'     => $classMetadata,
1747
            'parent'       => null,
1748
            'relation'     => null,
1749
            'map'          => null,
1750 772
            'nestingLevel' => $this->nestingLevel,
1751 772
            'token'        => $token
1752
        ];
1753
1754 772
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1755
1756 772
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1757
    }
1758
1759
    /**
1760
     * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1761
     *
1762
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1763
     */
1764 259
    public function JoinAssociationDeclaration()
1765
    {
1766 259
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1767
1768 259
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1769 5
            $this->match(Lexer::T_AS);
1770
        }
1771
1772 259
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1773 257
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1774
1775 257
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1776 257
        $field                  = $joinAssociationPathExpression->associationField;
1777
1778 257
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1779 257
        $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1780
1781
        // Building queryComponent
1782
        $joinQueryComponent = [
1783 257
            'metadata'     => $targetClass,
1784 257
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1785 257
            'relation'     => $class->getAssociationMapping($field),
1786
            'map'          => null,
1787 257
            'nestingLevel' => $this->nestingLevel,
1788 257
            'token'        => $this->lexer->lookahead
1789
        ];
1790
1791 257
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1792
1793 257
        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...
1794
    }
1795
1796
    /**
1797
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1798
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1799
     *
1800
     * @return \Doctrine\ORM\Query\AST\PartialObjectExpression
1801
     */
1802 11
    public function PartialObjectExpression()
1803
    {
1804 11
        $this->match(Lexer::T_PARTIAL);
1805
1806 11
        $partialFieldSet = [];
1807
1808 11
        $identificationVariable = $this->IdentificationVariable();
1809
1810 11
        $this->match(Lexer::T_DOT);
1811 11
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1812 11
        $this->match(Lexer::T_IDENTIFIER);
1813
1814 11
        $field = $this->lexer->token['value'];
1815
1816
        // First field in partial expression might be embeddable property
1817 11
        while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1818 1
            $this->match(Lexer::T_DOT);
1819 1
            $this->match(Lexer::T_IDENTIFIER);
1820 1
            $field .= '.'.$this->lexer->token['value'];
1821
        }
1822
1823 11
        $partialFieldSet[] = $field;
1824
1825 11
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1826 9
            $this->match(Lexer::T_COMMA);
1827 9
            $this->match(Lexer::T_IDENTIFIER);
1828
1829 9
            $field = $this->lexer->token['value'];
1830
1831 9
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1832 2
                $this->match(Lexer::T_DOT);
1833 2
                $this->match(Lexer::T_IDENTIFIER);
1834 2
                $field .= '.'.$this->lexer->token['value'];
1835
            }
1836
1837 9
            $partialFieldSet[] = $field;
1838
        }
1839
1840 11
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1841
1842 11
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1843
1844
        // Defer PartialObjectExpression validation
1845 11
        $this->deferredPartialObjectExpressions[] = [
1846 11
            'expression'   => $partialObjectExpression,
1847 11
            'nestingLevel' => $this->nestingLevel,
1848 11
            'token'        => $this->lexer->token,
1849
        ];
1850
1851 11
        return $partialObjectExpression;
1852
    }
1853
1854
    /**
1855
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1856
     *
1857
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1858
     */
1859 28
    public function NewObjectExpression()
1860
    {
1861 28
        $this->match(Lexer::T_NEW);
1862
1863 28
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1864 28
        $token = $this->lexer->token;
1865
1866 28
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1867
1868 28
        $args[] = $this->NewObjectArg();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$args was never initialized. Although not strictly required by PHP, it is generally a good practice to add $args = array(); before regardless.
Loading history...
1869
1870 28
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1871 24
            $this->match(Lexer::T_COMMA);
1872
1873 24
            $args[] = $this->NewObjectArg();
1874
        }
1875
1876 28
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1877
1878 28
        $expression = new AST\NewObjectExpression($className, $args);
1879
1880
        // Defer NewObjectExpression validation
1881 28
        $this->deferredNewObjectExpressions[] = [
1882 28
            'token'        => $token,
1883 28
            'expression'   => $expression,
1884 28
            'nestingLevel' => $this->nestingLevel,
1885
        ];
1886
1887 28
        return $expression;
1888
    }
1889
1890
    /**
1891
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1892
     *
1893
     * @return mixed
1894
     */
1895 28
    public function NewObjectArg()
1896
    {
1897 28
        $token = $this->lexer->lookahead;
1898 28
        $peek  = $this->lexer->glimpse();
1899
1900 28
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1901 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1902 2
            $expression = $this->Subselect();
1903 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1904
1905 2
            return $expression;
1906
        }
1907
1908 28
        return $this->ScalarExpression();
1909
    }
1910
1911
    /**
1912
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1913
     *
1914
     * @return \Doctrine\ORM\Query\AST\IndexBy
1915
     */
1916 12
    public function IndexBy()
1917
    {
1918 12
        $this->match(Lexer::T_INDEX);
1919 12
        $this->match(Lexer::T_BY);
1920 12
        $pathExpr = $this->StateFieldPathExpression();
1921
1922
        // Add the INDEX BY info to the query component
1923 12
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1924
1925 12
        return new AST\IndexBy($pathExpr);
1926
    }
1927
1928
    /**
1929
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1930
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1931
     *                      InstanceOfExpression
1932
     *
1933
     * @return mixed One of the possible expressions or subexpressions.
1934
     */
1935 163
    public function ScalarExpression()
1936
    {
1937 163
        $lookahead = $this->lexer->lookahead['type'];
1938 163
        $peek      = $this->lexer->glimpse();
1939
1940
        switch (true) {
1941 163
            case ($lookahead === Lexer::T_INTEGER):
1942 160
            case ($lookahead === Lexer::T_FLOAT):
1943
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1944 160
            case ($lookahead === Lexer::T_MINUS):
1945 160
            case ($lookahead === Lexer::T_PLUS):
1946 17
                return $this->SimpleArithmeticExpression();
1947
1948 160
            case ($lookahead === Lexer::T_STRING):
1949 13
                return $this->StringPrimary();
1950
1951 158
            case ($lookahead === Lexer::T_TRUE):
1952 158
            case ($lookahead === Lexer::T_FALSE):
1953 3
                $this->match($lookahead);
1954
1955 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1956
1957 158
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
1958
                switch (true) {
1959 1
                    case $this->isMathOperator($peek):
1960
                        // :param + u.value
1961 1
                        return $this->SimpleArithmeticExpression();
1962
                    default:
1963
                        return $this->InputParameter();
1964
                }
1965
1966 158
            case ($lookahead === Lexer::T_CASE):
1967 154
            case ($lookahead === Lexer::T_COALESCE):
1968 154
            case ($lookahead === Lexer::T_NULLIF):
1969
                // Since NULLIF and COALESCE can be identified as a function,
1970
                // we need to check these before checking for FunctionDeclaration
1971 8
                return $this->CaseExpression();
1972
1973 154
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1974 4
                return $this->SimpleArithmeticExpression();
1975
1976
            // this check must be done before checking for a filed path expression
1977 151
            case ($this->isFunction()):
1978 28
                $this->lexer->peek(); // "("
1979
1980
                switch (true) {
1981 28
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1982
                        // SUM(u.id) + COUNT(u.id)
1983 7
                        return $this->SimpleArithmeticExpression();
1984
1985
                    default:
1986
                        // IDENTITY(u)
1987 23
                        return $this->FunctionDeclaration();
1988
                }
1989
1990
                break;
1991
            // it is no function, so it must be a field path
1992 131
            case ($lookahead === Lexer::T_IDENTIFIER):
1993 131
                $this->lexer->peek(); // lookahead => '.'
1994 131
                $this->lexer->peek(); // lookahead => token after '.'
1995 131
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1996 131
                $this->lexer->resetPeek();
1997
1998 131
                if ($this->isMathOperator($peek)) {
1999 7
                    return $this->SimpleArithmeticExpression();
2000
                }
2001
2002 126
                return $this->StateFieldPathExpression();
2003
2004
            default:
2005
                $this->syntaxError();
2006
        }
2007
    }
2008
2009
    /**
2010
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
2011
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2012
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2013
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2014
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2015
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2016
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2017
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2018
     *
2019
     * @return mixed One of the possible expressions or subexpressions.
2020
     */
2021 19
    public function CaseExpression()
2022
    {
2023 19
        $lookahead = $this->lexer->lookahead['type'];
2024
2025
        switch ($lookahead) {
2026 19
            case Lexer::T_NULLIF:
2027 5
                return $this->NullIfExpression();
2028
2029 16
            case Lexer::T_COALESCE:
2030 2
                return $this->CoalesceExpression();
2031
2032 14
            case Lexer::T_CASE:
2033 14
                $this->lexer->resetPeek();
2034 14
                $peek = $this->lexer->peek();
2035
2036 14
                if ($peek['type'] === Lexer::T_WHEN) {
2037 9
                    return $this->GeneralCaseExpression();
2038
                }
2039
2040 5
                return $this->SimpleCaseExpression();
2041
2042
            default:
2043
                // Do nothing
2044
                break;
2045
        }
2046
2047
        $this->syntaxError();
2048
    }
2049
2050
    /**
2051
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2052
     *
2053
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2054
     */
2055 3
    public function CoalesceExpression()
2056
    {
2057 3
        $this->match(Lexer::T_COALESCE);
2058 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2059
2060
        // Process ScalarExpressions (1..N)
2061 3
        $scalarExpressions = [];
2062 3
        $scalarExpressions[] = $this->ScalarExpression();
2063
2064 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2065 3
            $this->match(Lexer::T_COMMA);
2066
2067 3
            $scalarExpressions[] = $this->ScalarExpression();
2068
        }
2069
2070 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2071
2072 3
        return new AST\CoalesceExpression($scalarExpressions);
2073
    }
2074
2075
    /**
2076
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2077
     *
2078
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2079
     */
2080 5
    public function NullIfExpression()
2081
    {
2082 5
        $this->match(Lexer::T_NULLIF);
2083 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2084
2085 5
        $firstExpression = $this->ScalarExpression();
2086 5
        $this->match(Lexer::T_COMMA);
2087 5
        $secondExpression = $this->ScalarExpression();
2088
2089 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2090
2091 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2092
    }
2093
2094
    /**
2095
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2096
     *
2097
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2098
     */
2099 9
    public function GeneralCaseExpression()
2100
    {
2101 9
        $this->match(Lexer::T_CASE);
2102
2103
        // Process WhenClause (1..N)
2104 9
        $whenClauses = [];
2105
2106
        do {
2107 9
            $whenClauses[] = $this->WhenClause();
2108 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2109
2110 9
        $this->match(Lexer::T_ELSE);
2111 9
        $scalarExpression = $this->ScalarExpression();
2112 9
        $this->match(Lexer::T_END);
2113
2114 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2115
    }
2116
2117
    /**
2118
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2119
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2120
     *
2121
     * @return AST\SimpleCaseExpression
2122
     */
2123 5
    public function SimpleCaseExpression()
2124
    {
2125 5
        $this->match(Lexer::T_CASE);
2126 5
        $caseOperand = $this->StateFieldPathExpression();
2127
2128
        // Process SimpleWhenClause (1..N)
2129 5
        $simpleWhenClauses = [];
2130
2131
        do {
2132 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2133 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2134
2135 5
        $this->match(Lexer::T_ELSE);
2136 5
        $scalarExpression = $this->ScalarExpression();
2137 5
        $this->match(Lexer::T_END);
2138
2139 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2140
    }
2141
2142
    /**
2143
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2144
     *
2145
     * @return \Doctrine\ORM\Query\AST\WhenClause
2146
     */
2147 9
    public function WhenClause()
2148
    {
2149 9
        $this->match(Lexer::T_WHEN);
2150 9
        $conditionalExpression = $this->ConditionalExpression();
2151 9
        $this->match(Lexer::T_THEN);
2152
2153 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2154
    }
2155
2156
    /**
2157
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2158
     *
2159
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2160
     */
2161 5
    public function SimpleWhenClause()
2162
    {
2163 5
        $this->match(Lexer::T_WHEN);
2164 5
        $conditionalExpression = $this->ScalarExpression();
2165 5
        $this->match(Lexer::T_THEN);
2166
2167 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2168
    }
2169
2170
    /**
2171
     * SelectExpression ::= (
2172
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2173
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2174
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2175
     *
2176
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2177
     */
2178 801
    public function SelectExpression()
2179
    {
2180 801
        $expression    = null;
2181 801
        $identVariable = null;
2182 801
        $peek          = $this->lexer->glimpse();
2183 801
        $lookaheadType = $this->lexer->lookahead['type'];
2184
2185
        switch (true) {
2186
            // ScalarExpression (u.name)
2187 801
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2188 103
                $expression = $this->ScalarExpression();
2189 103
                break;
2190
2191
            // IdentificationVariable (u)
2192 741
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2193 611
                $expression = $identVariable = $this->IdentificationVariable();
2194 611
                break;
2195
2196
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
2197 194
            case ($lookaheadType === Lexer::T_CASE):
2198 189
            case ($lookaheadType === Lexer::T_COALESCE):
2199 187
            case ($lookaheadType === Lexer::T_NULLIF):
2200 9
                $expression = $this->CaseExpression();
2201 9
                break;
2202
2203
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2204 185
            case ($this->isFunction()):
2205 104
                $this->lexer->peek(); // "("
2206
2207
                switch (true) {
2208 104
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2209
                        // SUM(u.id) + COUNT(u.id)
2210 2
                        $expression = $this->ScalarExpression();
2211 2
                        break;
2212
2213
                    default:
2214
                        // IDENTITY(u)
2215 102
                        $expression = $this->FunctionDeclaration();
2216 102
                        break;
2217
                }
2218
2219 104
                break;
2220
2221
            // PartialObjectExpression (PARTIAL u.{id, name})
2222 82
            case ($lookaheadType === Lexer::T_PARTIAL):
2223 11
                $expression    = $this->PartialObjectExpression();
2224 11
                $identVariable = $expression->identificationVariable;
2225 11
                break;
2226
2227
            // Subselect
2228 71
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2229 24
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2230 24
                $expression = $this->Subselect();
2231 24
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2232 24
                break;
2233
2234
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2235 47
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2236 43
            case ($lookaheadType === Lexer::T_INTEGER):
2237 41
            case ($lookaheadType === Lexer::T_STRING):
2238 32
            case ($lookaheadType === Lexer::T_FLOAT):
2239
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2240 32
            case ($lookaheadType === Lexer::T_MINUS):
2241 32
            case ($lookaheadType === Lexer::T_PLUS):
2242 16
                $expression = $this->SimpleArithmeticExpression();
2243 16
                break;
2244
2245
            // NewObjectExpression (New ClassName(id, name))
2246 31
            case ($lookaheadType === Lexer::T_NEW):
2247 28
                $expression = $this->NewObjectExpression();
2248 28
                break;
2249
2250
            default:
2251 3
                $this->syntaxError(
2252 3
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2253 3
                    $this->lexer->lookahead
2254
                );
2255
        }
2256
2257
        // [["AS"] ["HIDDEN"] AliasResultVariable]
2258 798
        $mustHaveAliasResultVariable = false;
2259
2260 798
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2261 124
            $this->match(Lexer::T_AS);
2262
2263 124
            $mustHaveAliasResultVariable = true;
2264
        }
2265
2266 798
        $hiddenAliasResultVariable = false;
2267
2268 798
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2269 11
            $this->match(Lexer::T_HIDDEN);
2270
2271 11
            $hiddenAliasResultVariable = true;
2272
        }
2273
2274 798
        $aliasResultVariable = null;
2275
2276 798
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2277 136
            $token = $this->lexer->lookahead;
2278 136
            $aliasResultVariable = $this->AliasResultVariable();
2279
2280
            // Include AliasResultVariable in query components.
2281 131
            $this->queryComponents[$aliasResultVariable] = [
2282 131
                'resultVariable' => $expression,
2283 131
                'nestingLevel'   => $this->nestingLevel,
2284 131
                'token'          => $token,
2285
            ];
2286
        }
2287
2288
        // AST
2289
2290 793
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2291
2292 793
        if ($identVariable) {
2293 619
            $this->identVariableExpressions[$identVariable] = $expr;
2294
        }
2295
2296 793
        return $expr;
2297
    }
2298
2299
    /**
2300
     * SimpleSelectExpression ::= (
2301
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2302
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2303
     * ) [["AS"] AliasResultVariable]
2304
     *
2305
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2306
     */
2307 50
    public function SimpleSelectExpression()
2308
    {
2309 50
        $peek = $this->lexer->glimpse();
2310
2311 50
        switch ($this->lexer->lookahead['type']) {
2312 50
            case Lexer::T_IDENTIFIER:
2313
                switch (true) {
2314 19
                    case ($peek['type'] === Lexer::T_DOT):
2315 16
                        $expression = $this->StateFieldPathExpression();
2316
2317 16
                        return new AST\SimpleSelectExpression($expression);
2318
2319 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2320 2
                        $expression = $this->IdentificationVariable();
2321
2322 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

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

3204
        $likeExpr = new AST\LikeExpression($stringExpr, /** @scrutinizer ignore-type */ $stringPattern, $escapeChar);
Loading history...
3205 14
        $likeExpr->not = $not;
3206
3207 14
        return $likeExpr;
3208
    }
3209
3210
    /**
3211
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3212
     *
3213
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3214
     */
3215 16
    public function NullComparisonExpression()
3216
    {
3217
        switch (true) {
3218 16
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3219
                $this->match(Lexer::T_INPUT_PARAMETER);
3220
3221
                $expr = new AST\InputParameter($this->lexer->token['value']);
3222
                break;
3223
3224 16
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3225 1
                $expr = $this->NullIfExpression();
3226 1
                break;
3227
3228 16
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3229 1
                $expr = $this->CoalesceExpression();
3230 1
                break;
3231
3232 16
            case $this->isFunction():
3233 2
                $expr = $this->FunctionDeclaration();
3234 2
                break;
3235
3236
            default:
3237
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3238 15
                $glimpse = $this->lexer->glimpse();
3239
3240 15
                if ($glimpse['type'] === Lexer::T_DOT) {
3241 11
                    $expr = $this->SingleValuedPathExpression();
3242
3243
                    // Leave switch statement
3244 11
                    break;
3245
                }
3246
3247 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3248
3249
                // Validate existing component
3250 4
                if ( ! isset($this->queryComponents[$lookaheadValue])) {
3251
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3252
                }
3253
3254
                // Validate SingleValuedPathExpression (ie.: "product")
3255 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3256 1
                    $expr = $this->SingleValuedPathExpression();
3257 1
                    break;
3258
                }
3259
3260
                // Validating ResultVariable
3261 3
                if ( ! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3262
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3263
                }
3264
3265 3
                $expr = $this->ResultVariable();
3266 3
                break;
3267
        }
3268
3269 16
        $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

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