Failed Conditions
Pull Request — 2.6 (#7606)
by
unknown
07:21
created

Parser::UpdateItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 866
    public function __construct(Query $query)
188
    {
189 866
        $this->query        = $query;
190 866
        $this->em           = $query->getEntityManager();
191 866
        $this->lexer        = new Lexer($query->getDQL());
192 866
        $this->parserResult = new ParserResult();
193 866
    }
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 866
    public function getAST()
258
    {
259
        // Parse & build AST
260 866
        $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 824
        $this->processDeferredIdentificationVariables();
265
266 822
        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 820
        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 606
            $this->processDeferredPathExpressions();
272
        }
273
274 817
        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 817
        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 813
        $this->processRootEntityAliasSelected();
283
284
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
285 812
        $this->fixIdentificationVariableOrder($AST);
286
287 812
        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 877
    public function match($token)
303
    {
304 877
        $lookaheadType = $this->lexer->lookahead['type'];
305
306
        // Short-circuit on first condition, usually types match
307 877
        if ($lookaheadType === $token) {
308 869
            $this->lexer->moveNext();
309 869
            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 866
    public function parse()
358
    {
359 866
        $AST = $this->getAST();
360
361 812
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
362 99
            $this->customTreeWalkers = $customWalkers;
363
        }
364
365 812
        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 812
        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 806
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
395 806
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
396
397
        // Assign an SQL executor to the parser result
398 806
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
399
400 798
        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 812
    private function fixIdentificationVariableOrder($AST)
415
    {
416 812
        if (count($this->identVariableExpressions) <= 1) {
417 635
            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;
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 362
    private function isMathOperator($token)
538
    {
539 362
        return 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['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 824
    private function processDeferredIdentificationVariables()
586
    {
587 824
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
588 800
            $identVariable = $deferredItem['expression'];
589
590
            // Check if IdentificationVariable exists in queryComponents
591 800
            if ( ! isset($this->queryComponents[$identVariable])) {
592 1
                $this->semanticalError(
593 1
                    "'$identVariable' is not defined.", $deferredItem['token']
594
                );
595
            }
596
597 800
            $qComp = $this->queryComponents[$identVariable];
598
599
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
600 800
            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 800
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
608 1
                $this->semanticalError(
609 800
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
610
                );
611
            }
612
        }
613 822
    }
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 606
    private function processDeferredPathExpressions()
749
    {
750 606
        foreach ($this->deferredPathExpressions as $deferredItem) {
751 606
            $pathExpression = $deferredItem['expression'];
752
753 606
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
754 606
            $class = $qComp['metadata'];
755
756 606
            if (($field = $pathExpression->field) === null) {
757 40
                $field = $pathExpression->field = $class->identifier[0];
758
            }
759
760
            // Check if field or association exists
761 606
            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 605
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
769
770 605
            if (isset($class->associationMappings[$field])) {
771 88
                $assoc = $class->associationMappings[$field];
772
773 88
                $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
774 67
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
775 88
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
776
            }
777
778
            // Validate if PathExpression is one of the expected types
779 605
            $expectedType = $pathExpression->expectedType;
780
781 605
            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 603
            $pathExpression->type = $fieldType;
811
        }
812 603
    }
813
814
    /**
815
     * @return void
816
     */
817 813
    private function processRootEntityAliasSelected()
818
    {
819 813
        if ( ! count($this->identVariableExpressions)) {
820 240
            return;
821
        }
822
823 584
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
824 584
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
825 584
                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 866
    public function QueryLanguage()
840
    {
841 866
        $statement = null;
842
843 866
        $this->lexer->moveNext();
844
845 866
        switch ($this->lexer->lookahead['type']) {
846 866
            case Lexer::T_SELECT:
847 800
                $statement = $this->SelectStatement();
848 762
                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 828
        if ($this->lexer->lookahead !== null) {
865 4
            $this->syntaxError('end of string');
866
        }
867
868 824
        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 800
    public function SelectStatement()
877
    {
878 800
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
879
880 766
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
881 763
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
882 762
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
883 762
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
884
885 762
        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 831
    public function IdentificationVariable()
922
    {
923 831
        $this->match(Lexer::T_IDENTIFIER);
924
925 831
        $identVariable = $this->lexer->token['value'];
926
927 831
        $this->deferredIdentificationVariables[] = [
928 831
            'expression'   => $identVariable,
929 831
            'nestingLevel' => $this->nestingLevel,
930 831
            'token'        => $this->lexer->token,
931
        ];
932
933 831
        return $identVariable;
934
    }
935
936
    /**
937
     * AliasIdentificationVariable = identifier
938
     *
939
     * @return string
940
     */
941 834
    public function AliasIdentificationVariable()
942
    {
943 834
        $this->match(Lexer::T_IDENTIFIER);
944
945 834
        $aliasIdentVariable = $this->lexer->token['value'];
946 834
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
947
948 834
        if ($exists) {
949 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
950
        }
951
952 834
        return $aliasIdentVariable;
953
    }
954
955
    /**
956
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
957
     *
958
     * @return string
959
     */
960 856
    public function AbstractSchemaName()
961
    {
962 856
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
963 838
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
964
965 838
            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 850
    private function validateAbstractSchemaName($schemaName)
989
    {
990 850
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
991 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
992
        }
993 835
    }
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 616
    public function PathExpression($expectedTypes)
1077
    {
1078 616
        $identVariable = $this->IdentificationVariable();
1079 616
        $field = null;
1080
1081 616
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1082 609
            $this->match(Lexer::T_DOT);
1083 609
            $this->match(Lexer::T_IDENTIFIER);
1084
1085 609
            $field = $this->lexer->token['value'];
1086
1087 609
            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 616
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1096
1097
        // Defer PathExpression validation if requested to be deferred
1098 616
        $this->deferredPathExpressions[] = [
1099 616
            'expression'   => $pathExpr,
1100 616
            'nestingLevel' => $this->nestingLevel,
1101 616
            'token'        => $this->lexer->token,
1102
        ];
1103
1104 616
        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 21
    public function CollectionValuedPathExpression()
1159
    {
1160 21
        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 800
    public function SelectClause()
1169
    {
1170 800
        $isDistinct = false;
1171 800
        $this->match(Lexer::T_SELECT);
1172
1173
        // Check for DISTINCT
1174 800
        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 800
        $selectExpressions = [];
1182 800
        $selectExpressions[] = $this->SelectExpression();
1183
1184 792
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1185 301
            $this->match(Lexer::T_COMMA);
1186
1187 301
            $selectExpressions[] = $this->SelectExpression();
1188
        }
1189
1190 791
        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 791
    public function FromClause()
1315
    {
1316 791
        $this->match(Lexer::T_FROM);
1317
1318 786
        $identificationVariableDeclarations = [];
1319 786
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1320
1321 766
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1322 6
            $this->match(Lexer::T_COMMA);
1323
1324 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1325
        }
1326
1327 766
        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 349
    public function WhereClause()
1357
    {
1358 349
        $this->match(Lexer::T_WHERE);
1359
1360 349
        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['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['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 788
    public function IdentificationVariableDeclaration()
1582
    {
1583 788
        $joins                    = [];
1584 788
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1585 771
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1586 8
            ? $this->IndexBy()
1587 771
            : null;
1588
1589 771
        $rangeVariableDeclaration->isRoot = true;
1590
1591
        while (
1592 771
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1593 771
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1594 771
            $this->lexer->isNextToken(Lexer::T_JOIN)
1595
        ) {
1596 281
            $joins[] = $this->Join();
1597
        }
1598
1599 768
        return new AST\IdentificationVariableDeclaration(
1600 768
            $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 788
    public function RangeVariableDeclaration()
1727
    {
1728 788
        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 787
        $abstractSchemaName = $this->AbstractSchemaName();
1733
1734 786
        $this->validateAbstractSchemaName($abstractSchemaName);
1735
1736 771
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1737 6
            $this->match(Lexer::T_AS);
1738
        }
1739
1740 771
        $token = $this->lexer->lookahead;
1741 771
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1742 771
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1743
1744
        // Building queryComponent
1745
        $queryComponent = [
1746 771
            'metadata'     => $classMetadata,
1747
            'parent'       => null,
1748
            'relation'     => null,
1749
            'map'          => null,
1750 771
            'nestingLevel' => $this->nestingLevel,
1751 771
            'token'        => $token
1752
        ];
1753
1754 771
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1755
1756 771
        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 800
    public function SelectExpression()
2179
    {
2180 800
        $expression    = null;
2181 800
        $identVariable = null;
2182 800
        $peek          = $this->lexer->glimpse();
2183 800
        $lookaheadType = $this->lexer->lookahead['type'];
2184
2185
        switch (true) {
2186
            // ScalarExpression (u.name)
2187 800
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2188 103
                $expression = $this->ScalarExpression();
2189 103
                break;
2190
2191
            // IdentificationVariable (u)
2192 740
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2193 610
                $expression = $identVariable = $this->IdentificationVariable();
2194 610
                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 797
        $mustHaveAliasResultVariable = false;
2259
2260 797
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2261 124
            $this->match(Lexer::T_AS);
2262
2263 124
            $mustHaveAliasResultVariable = true;
2264
        }
2265
2266 797
        $hiddenAliasResultVariable = false;
2267
2268 797
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2269 11
            $this->match(Lexer::T_HIDDEN);
2270
2271 11
            $hiddenAliasResultVariable = true;
2272
        }
2273
2274 797
        $aliasResultVariable = null;
2275
2276 797
        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 792
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2291
2292 792
        if ($identVariable) {
2293 618
            $this->identVariableExpressions[$identVariable] = $expr;
2294
        }
2295
2296 792
        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 392
    public function ConditionalExpression()
2391
    {
2392 392
        $conditionalTerms = [];
2393 392
        $conditionalTerms[] = $this->ConditionalTerm();
2394
2395 389
        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 389
        if (count($conditionalTerms) == 1) {
2404 381
            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 392
    public function ConditionalTerm()
2416
    {
2417 392
        $conditionalFactors = [];
2418 392
        $conditionalFactors[] = $this->ConditionalFactor();
2419
2420 389
        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 389
        if (count($conditionalFactors) == 1) {
2429 369
            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 392
    public function ConditionalFactor()
2441
    {
2442 392
        $not = false;
2443
2444 392
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2445 6
            $this->match(Lexer::T_NOT);
2446
2447 6
            $not = true;
2448
        }
2449
2450 392
        $conditionalPrimary = $this->ConditionalPrimary();
2451
2452
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2453
        // if only one AST\ConditionalPrimary is defined
2454 389
        if ( ! $not) {
2455 387
            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 392
    public function ConditionalPrimary()
2470
    {
2471 392
        $condPrimary = new AST\ConditionalPrimary;
2472
2473 392
        if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2474 383
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2475
2476 380
            return $condPrimary;
2477
        }
2478
2479
        // Peek beyond the matching closing parenthesis ')'
2480 25
        $peek = $this->peekBeyondClosingParenthesis();
2481
2482 25
        if (in_array($peek['value'], ["=",  "<", "<=", "<>", ">", ">=", "!="]) ||
2483 22
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
2484 25
            $this->isMathOperator($peek)) {
2485 15
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2486
2487 15
            return $condPrimary;
2488
        }
2489
2490 21
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2491 21
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2492 21
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2493
2494 21
        return $condPrimary;
2495
    }
2496
2497
    /**
2498
     * SimpleConditionalExpression ::=
2499
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2500
     *      InExpression | NullComparisonExpression | ExistsExpression |
2501
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2502
     *      InstanceOfExpression
2503
     */
2504 392
    public function SimpleConditionalExpression()
2505
    {
2506 392
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2507 7
            return $this->ExistsExpression();
2508
        }
2509
2510 392
        $token      = $this->lexer->lookahead;
2511 392
        $peek       = $this->lexer->glimpse();
2512 392
        $lookahead  = $token;
2513
2514 392
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2515
            $token = $this->lexer->glimpse();
2516
        }
2517
2518 392
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2519
            // Peek beyond the matching closing parenthesis.
2520 368
            $beyond = $this->lexer->peek();
2521
2522 368
            switch ($peek['value']) {
2523 368
                case '(':
2524
                    // Peeks beyond the matched closing parenthesis.
2525 33
                    $token = $this->peekBeyondClosingParenthesis(false);
2526
2527 33
                    if ($token['type'] === Lexer::T_NOT) {
2528 3
                        $token = $this->lexer->peek();
2529
                    }
2530
2531 33
                    if ($token['type'] === Lexer::T_IS) {
2532 2
                        $lookahead = $this->lexer->peek();
2533
                    }
2534 33
                    break;
2535
2536
                default:
2537
                    // Peek beyond the PathExpression or InputParameter.
2538 341
                    $token = $beyond;
2539
2540 341
                    while ($token['value'] === '.') {
2541 297
                        $this->lexer->peek();
2542
2543 297
                        $token = $this->lexer->peek();
2544
                    }
2545
2546
                    // Also peek beyond a NOT if there is one.
2547 341
                    if ($token['type'] === Lexer::T_NOT) {
2548 11
                        $token = $this->lexer->peek();
2549
                    }
2550
2551
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2552 341
                    $lookahead = $this->lexer->peek();
2553
            }
2554
2555
            // Also peek beyond a NOT if there is one.
2556 368
            if ($lookahead['type'] === Lexer::T_NOT) {
2557 9
                $lookahead = $this->lexer->peek();
2558
            }
2559
2560 368
            $this->lexer->resetPeek();
2561 32
        } elseif ($token['type'] === Lexer::T_CASE || ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_CASE)) {
2562
            // Need to discover if it's a regular or an "in" comparison
2563
            do {
2564
                $token = $this->lexer->peek();
2565
            } while ($token['type'] != Lexer::T_END);
2566
            while ($token = $this->lexer->peek()) {
2567
                if ($token['type'] !== Lexer::T_CLOSE_PARENTHESIS) {
2568
                    break;
2569
                }
2570
            }
2571
            $lookahead = $this->lexer->peek();
2572
            $this->lexer->resetPeek();
2573
        }
2574
2575 392
        if ($token['type'] === Lexer::T_BETWEEN) {
2576 8
            return $this->BetweenExpression();
2577
        }
2578
2579 386
        if ($token['type'] === Lexer::T_LIKE) {
2580 14
            return $this->LikeExpression();
2581
        }
2582
2583 373
        if ($token['type'] === Lexer::T_IN) {
2584 35
            return $this->InExpression();
2585
        }
2586
2587 347
        if ($token['type'] === Lexer::T_INSTANCE) {
2588 17
            return $this->InstanceOfExpression();
2589
        }
2590
2591 330
        if ($token['type'] === Lexer::T_MEMBER) {
2592 7
            return $this->CollectionMemberExpression();
2593
        }
2594
2595 323
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2596 16
            return $this->NullComparisonExpression();
2597
        }
2598
2599 311
        if ($token['type'] === Lexer::T_IS  && $lookahead['type'] === Lexer::T_EMPTY) {
2600 4
            return $this->EmptyCollectionComparisonExpression();
2601
        }
2602
2603 307
        return $this->ComparisonExpression();
2604
    }
2605
2606
    /**
2607
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2608
     *
2609
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2610
     */
2611 4
    public function EmptyCollectionComparisonExpression()
2612
    {
2613 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2614 4
            $this->CollectionValuedPathExpression()
2615
        );
2616 4
        $this->match(Lexer::T_IS);
2617
2618 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2619 2
            $this->match(Lexer::T_NOT);
2620 2
            $emptyCollectionCompExpr->not = true;
2621
        }
2622
2623 4
        $this->match(Lexer::T_EMPTY);
2624
2625 4
        return $emptyCollectionCompExpr;
2626
    }
2627
2628
    /**
2629
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2630
     *
2631
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2632
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2633
     *
2634
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2635
     */
2636 7
    public function CollectionMemberExpression()
2637
    {
2638 7
        $not        = false;
2639 7
        $entityExpr = $this->EntityExpression();
2640
2641 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2642
            $this->match(Lexer::T_NOT);
2643
2644
            $not = true;
2645
        }
2646
2647 7
        $this->match(Lexer::T_MEMBER);
2648
2649 7
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2650 7
            $this->match(Lexer::T_OF);
2651
        }
2652
2653 7
        $collMemberExpr = new AST\CollectionMemberExpression(
2654 7
            $entityExpr, $this->CollectionValuedPathExpression()
2655
        );
2656 7
        $collMemberExpr->not = $not;
2657
2658 7
        return $collMemberExpr;
2659
    }
2660
2661
    /**
2662
     * Literal ::= string | char | integer | float | boolean
2663
     *
2664
     * @return \Doctrine\ORM\Query\AST\Literal
2665
     */
2666 189
    public function Literal()
2667
    {
2668 189
        switch ($this->lexer->lookahead['type']) {
2669 189
            case Lexer::T_STRING:
2670 48
                $this->match(Lexer::T_STRING);
2671
2672 48
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2673 149
            case Lexer::T_INTEGER:
2674 9
            case Lexer::T_FLOAT:
2675 141
                $this->match(
2676 141
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2677
                );
2678
2679 141
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2680 8
            case Lexer::T_TRUE:
2681 4
            case Lexer::T_FALSE:
2682 8
                $this->match(
2683 8
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2684
                );
2685
2686 8
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2687
            default:
2688
                $this->syntaxError('Literal');
2689
        }
2690
    }
2691
2692
    /**
2693
     * InParameter ::= Literal | InputParameter
2694
     *
2695
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2696
     */
2697 26
    public function InParameter()
2698
    {
2699 26
        if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2700 14
            return $this->InputParameter();
2701
        }
2702
2703 12
        return $this->Literal();
2704
    }
2705
2706
    /**
2707
     * InputParameter ::= PositionalParameter | NamedParameter
2708
     *
2709
     * @return \Doctrine\ORM\Query\AST\InputParameter
2710
     */
2711 174
    public function InputParameter()
2712
    {
2713 174
        $this->match(Lexer::T_INPUT_PARAMETER);
2714
2715 174
        return new AST\InputParameter($this->lexer->token['value']);
2716
    }
2717
2718
    /**
2719
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2720
     *
2721
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2722
     */
2723 341
    public function ArithmeticExpression()
2724
    {
2725 341
        $expr = new AST\ArithmeticExpression;
2726
2727 341
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2728 19
            $peek = $this->lexer->glimpse();
2729
2730 19
            if ($peek['type'] === Lexer::T_SELECT) {
2731 7
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2732 7
                $expr->subselect = $this->Subselect();
2733 7
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2734
2735 7
                return $expr;
2736
            }
2737
        }
2738
2739 341
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2740
2741 341
        return $expr;
2742
    }
2743
2744
    /**
2745
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2746
     *
2747
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2748
     */
2749 449
    public function SimpleArithmeticExpression()
2750
    {
2751 449
        $terms = [];
2752 449
        $terms[] = $this->ArithmeticTerm();
2753
2754 449
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2755 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2756
2757 21
            $terms[] = $this->lexer->token['value'];
2758 21
            $terms[] = $this->ArithmeticTerm();
2759
        }
2760
2761
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2762
        // if only one AST\ArithmeticTerm is defined
2763 449
        if (count($terms) == 1) {
2764 444
            return $terms[0];
2765
        }
2766
2767 21
        return new AST\SimpleArithmeticExpression($terms);
2768
    }
2769
2770
    /**
2771
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2772
     *
2773
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2774
     */
2775 449
    public function ArithmeticTerm()
2776
    {
2777 449
        $factors = [];
2778 449
        $factors[] = $this->ArithmeticFactor();
2779
2780 449
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2781 53
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2782
2783 53
            $factors[] = $this->lexer->token['value'];
2784 53
            $factors[] = $this->ArithmeticFactor();
2785
        }
2786
2787
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2788
        // if only one AST\ArithmeticFactor is defined
2789 449
        if (count($factors) == 1) {
2790 421
            return $factors[0];
2791
        }
2792
2793 53
        return new AST\ArithmeticTerm($factors);
2794
    }
2795
2796
    /**
2797
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2798
     *
2799
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2800
     */
2801 449
    public function ArithmeticFactor()
2802
    {
2803 449
        $sign = null;
2804
2805 449
        if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2806 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2807 3
            $sign = $isPlus;
2808
        }
2809
2810 449
        $primary = $this->ArithmeticPrimary();
2811
2812
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2813
        // if only one AST\ArithmeticPrimary is defined
2814 449
        if ($sign === null) {
2815 448
            return $primary;
2816
        }
2817
2818 3
        return new AST\ArithmeticFactor($primary, $sign);
2819
    }
2820
2821
    /**
2822
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2823
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2824
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2825
     *          | InputParameter | CaseExpression
2826
     */
2827 464
    public function ArithmeticPrimary()
2828
    {
2829 464
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2830 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2831
2832 25
            $expr = $this->SimpleArithmeticExpression();
2833
2834 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2835
2836 25
            return new AST\ParenthesisExpression($expr);
2837
        }
2838
2839 464
        switch ($this->lexer->lookahead['type']) {
2840 464
            case Lexer::T_COALESCE:
2841 464
            case Lexer::T_NULLIF:
2842 464
            case Lexer::T_CASE:
2843 4
                return $this->CaseExpression();
2844
2845 464
            case Lexer::T_IDENTIFIER:
2846 434
                $peek = $this->lexer->glimpse();
2847
2848 434
                if ($peek['value'] == '(') {
2849 39
                    return $this->FunctionDeclaration();
2850
                }
2851
2852 403
                if ($peek['value'] == '.') {
2853 392
                    return $this->SingleValuedPathExpression();
2854
                }
2855
2856 47
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2857 10
                    return $this->ResultVariable();
2858
                }
2859
2860 39
                return $this->StateFieldPathExpression();
2861
2862 329
            case Lexer::T_INPUT_PARAMETER:
2863 155
                return $this->InputParameter();
2864
2865
            default:
2866 183
                $peek = $this->lexer->glimpse();
2867
2868 183
                if ($peek['value'] == '(') {
2869 18
                    return $this->FunctionDeclaration();
2870
                }
2871
2872 179
                return $this->Literal();
2873
        }
2874
    }
2875
2876
    /**
2877
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2878
     *
2879
     * @return \Doctrine\ORM\Query\AST\Subselect |
2880
     *         string
2881
     */
2882 14
    public function StringExpression()
2883
    {
2884 14
        $peek = $this->lexer->glimpse();
2885
2886
        // Subselect
2887 14
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2888
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2889
            $expr = $this->Subselect();
2890
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2891
2892
            return $expr;
2893
        }
2894
2895
        // ResultVariable (string)
2896 14
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2897 14
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2898 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...
2899
        }
2900
2901 12
        return $this->StringPrimary();
2902
    }
2903
2904
    /**
2905
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2906
     */
2907 65
    public function StringPrimary()
2908
    {
2909 65
        $lookaheadType = $this->lexer->lookahead['type'];
2910
2911
        switch ($lookaheadType) {
2912 65
            case Lexer::T_IDENTIFIER:
2913 35
                $peek = $this->lexer->glimpse();
2914
2915 35
                if ($peek['value'] == '.') {
2916 35
                    return $this->StateFieldPathExpression();
2917
                }
2918
2919 8
                if ($peek['value'] == '(') {
2920
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2921 8
                    return $this->FunctionDeclaration();
2922
                }
2923
2924
                $this->syntaxError("'.' or '('");
2925
                break;
2926
2927 46
            case Lexer::T_STRING:
2928 43
                $this->match(Lexer::T_STRING);
2929
2930 43
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2931
2932 5
            case Lexer::T_INPUT_PARAMETER:
2933 2
                return $this->InputParameter();
2934
2935 3
            case Lexer::T_CASE:
2936 3
            case Lexer::T_COALESCE:
2937 3
            case Lexer::T_NULLIF:
2938
                return $this->CaseExpression();
2939
            default:
2940 3
                if ($this->isAggregateFunction($lookaheadType)) {
2941 3
                    return $this->AggregateExpression();
2942
                }
2943
        }
2944
2945
        $this->syntaxError(
2946
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2947
        );
2948
    }
2949
2950
    /**
2951
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2952
     *
2953
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2954
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2955
     */
2956 7
    public function EntityExpression()
2957
    {
2958 7
        $glimpse = $this->lexer->glimpse();
2959
2960 7
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2961 1
            return $this->SingleValuedAssociationPathExpression();
2962
        }
2963
2964 6
        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...
2965
    }
2966
2967
    /**
2968
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2969
     *
2970
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2971
     */
2972 6
    public function SimpleEntityExpression()
2973
    {
2974 6
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2975 5
            return $this->InputParameter();
2976
        }
2977
2978 1
        return $this->StateFieldPathExpression();
2979
    }
2980
2981
    /**
2982
     * AggregateExpression ::=
2983
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2984
     *
2985
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2986
     */
2987 92
    public function AggregateExpression()
2988
    {
2989 92
        $lookaheadType = $this->lexer->lookahead['type'];
2990 92
        $isDistinct = false;
2991
2992 92
        if ( ! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM])) {
2993
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2994
        }
2995
2996 92
        $this->match($lookaheadType);
2997 92
        $functionName = $this->lexer->token['value'];
2998 92
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2999
3000 92
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
3001 3
            $this->match(Lexer::T_DISTINCT);
3002 3
            $isDistinct = true;
3003
        }
3004
3005 92
        $pathExp = $this->SimpleArithmeticExpression();
3006
3007 92
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3008
3009 92
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
3010
    }
3011
3012
    /**
3013
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
3014
     *
3015
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
3016
     */
3017 3
    public function QuantifiedExpression()
3018
    {
3019 3
        $lookaheadType = $this->lexer->lookahead['type'];
3020 3
        $value = $this->lexer->lookahead['value'];
3021
3022 3
        if ( ! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME])) {
3023
            $this->syntaxError('ALL, ANY or SOME');
3024
        }
3025
3026 3
        $this->match($lookaheadType);
3027 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3028
3029 3
        $qExpr = new AST\QuantifiedExpression($this->Subselect());
3030 3
        $qExpr->type = $value;
3031
3032 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3033
3034 3
        return $qExpr;
3035
    }
3036
3037
    /**
3038
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
3039
     *
3040
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
3041
     */
3042 8
    public function BetweenExpression()
3043
    {
3044 8
        $not = false;
3045 8
        $arithExpr1 = $this->ArithmeticExpression();
3046
3047 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3048 3
            $this->match(Lexer::T_NOT);
3049 3
            $not = true;
3050
        }
3051
3052 8
        $this->match(Lexer::T_BETWEEN);
3053 8
        $arithExpr2 = $this->ArithmeticExpression();
3054 8
        $this->match(Lexer::T_AND);
3055 8
        $arithExpr3 = $this->ArithmeticExpression();
3056
3057 8
        $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3058 8
        $betweenExpr->not = $not;
3059
3060 8
        return $betweenExpr;
3061
    }
3062
3063
    /**
3064
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3065
     *
3066
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3067
     */
3068 307
    public function ComparisonExpression()
3069
    {
3070 307
        $this->lexer->glimpse();
3071
3072 307
        $leftExpr  = $this->ArithmeticExpression();
3073 307
        $operator  = $this->ComparisonOperator();
3074 307
        $rightExpr = ($this->isNextAllAnySome())
3075 3
            ? $this->QuantifiedExpression()
3076 307
            : $this->ArithmeticExpression();
3077
3078 305
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3079
    }
3080
3081
    /**
3082
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3083
     *
3084
     * @return \Doctrine\ORM\Query\AST\InExpression
3085
     */
3086 35
    public function InExpression()
3087
    {
3088 35
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3089
3090 35
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3091 6
            $this->match(Lexer::T_NOT);
3092 6
            $inExpression->not = true;
3093
        }
3094
3095 35
        $this->match(Lexer::T_IN);
3096 35
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3097
3098 35
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3099 9
            $inExpression->subselect = $this->Subselect();
3100
        } else {
3101 26
            $literals = [];
3102 26
            $literals[] = $this->InParameter();
3103
3104 26
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3105 16
                $this->match(Lexer::T_COMMA);
3106 16
                $literals[] = $this->InParameter();
3107
            }
3108
3109 26
            $inExpression->literals = $literals;
3110
        }
3111
3112 34
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3113
3114 34
        return $inExpression;
3115
    }
3116
3117
    /**
3118
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3119
     *
3120
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3121
     */
3122 17
    public function InstanceOfExpression()
3123
    {
3124 17
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3125
3126 17
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3127 1
            $this->match(Lexer::T_NOT);
3128 1
            $instanceOfExpression->not = true;
3129
        }
3130
3131 17
        $this->match(Lexer::T_INSTANCE);
3132 17
        $this->match(Lexer::T_OF);
3133
3134 17
        $exprValues = [];
3135
3136 17
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3137 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3138
3139 2
            $exprValues[] = $this->InstanceOfParameter();
3140
3141 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3142 2
                $this->match(Lexer::T_COMMA);
3143
3144 2
                $exprValues[] = $this->InstanceOfParameter();
3145
            }
3146
3147 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3148
3149 2
            $instanceOfExpression->value = $exprValues;
3150
3151 2
            return $instanceOfExpression;
3152
        }
3153
3154 15
        $exprValues[] = $this->InstanceOfParameter();
3155
3156 15
        $instanceOfExpression->value = $exprValues;
3157
3158 15
        return $instanceOfExpression;
3159
    }
3160
3161
    /**
3162
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3163
     *
3164
     * @return mixed
3165
     */
3166 17
    public function InstanceOfParameter()
3167
    {
3168 17
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3169 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3170
3171 6
            return new AST\InputParameter($this->lexer->token['value']);
3172
        }
3173
3174 11
        $abstractSchemaName = $this->AbstractSchemaName();
3175
3176 11
        $this->validateAbstractSchemaName($abstractSchemaName);
3177
3178 11
        return $abstractSchemaName;
3179
    }
3180
3181
    /**
3182
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3183
     *
3184
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3185
     */
3186 14
    public function LikeExpression()
3187
    {
3188 14
        $stringExpr = $this->StringExpression();
3189 14
        $not = false;
3190
3191 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3192 3
            $this->match(Lexer::T_NOT);
3193 3
            $not = true;
3194
        }
3195
3196 14
        $this->match(Lexer::T_LIKE);
3197
3198 14
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3199 4
            $this->match(Lexer::T_INPUT_PARAMETER);
3200 4
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3201
        } else {
3202 11
            $stringPattern = $this->StringPrimary();
3203
        }
3204
3205 14
        $escapeChar = null;
3206
3207 14
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3208 2
            $this->match(Lexer::T_ESCAPE);
3209 2
            $this->match(Lexer::T_STRING);
3210
3211 2
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3212
        }
3213
3214 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

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

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