Failed Conditions
Pull Request — 2.6 (#7180)
by Ben
11:16
created

Parser::SelectClause()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 11
nc 4
nop 0
dl 0
loc 23
ccs 12
cts 12
cp 1
crap 3
rs 9.0856
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 859
    public function __construct(Query $query)
188
    {
189 859
        $this->query        = $query;
190 859
        $this->em           = $query->getEntityManager();
191 859
        $this->lexer        = new Lexer($query->getDQL());
192 859
        $this->parserResult = new ParserResult();
193 859
    }
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 127
    public function setCustomOutputTreeWalker($className)
204
    {
205 127
        $this->customOutputWalker = $className;
0 ignored issues
show
Documentation Bug introduced by
It seems like $className of type string is incompatible with the declared type Doctrine\ORM\Query\TreeWalker of property $customOutputWalker.

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

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

Loading history...
206 127
    }
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 28
    public function getLexer()
226
    {
227 28
        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 859
    public function getAST()
258
    {
259
        // Parse & build AST
260 859
        $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 817
        $this->processDeferredIdentificationVariables();
265
266 815
        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 813
        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 601
            $this->processDeferredPathExpressions();
272
        }
273
274 810
        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 810
        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 806
        $this->processRootEntityAliasSelected();
283
284
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
285 805
        $this->fixIdentificationVariableOrder($AST);
286
287 805
        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 870
    public function match($token)
303
    {
304 870
        $lookaheadType = $this->lexer->lookahead['type'];
305
306
        // Short-circuit on first condition, usually types match
307 870
        if ($lookaheadType === $token) {
308 862
            $this->lexer->moveNext();
309 862
            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 859
    public function parse()
358
    {
359 859
        $AST = $this->getAST();
360
361 805
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
362 96
            $this->customTreeWalkers = $customWalkers;
363
        }
364
365 805
        if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
366 79
            $this->customOutputWalker = $customOutputWalker;
367
        }
368
369
        // Run any custom tree walkers over the AST
370 805
        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 95
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
372
373 95
            foreach ($this->customTreeWalkers as $walker) {
374 95
                $treeWalkerChain->addTreeWalker($walker);
375
            }
376
377
            switch (true) {
378 95
                case ($AST instanceof AST\UpdateStatement):
379
                    $treeWalkerChain->walkUpdateStatement($AST);
380
                    break;
381
382 95
                case ($AST instanceof AST\DeleteStatement):
383
                    $treeWalkerChain->walkDeleteStatement($AST);
384
                    break;
385
386 95
                case ($AST instanceof AST\SelectStatement):
387
                default:
388 95
                    $treeWalkerChain->walkSelectStatement($AST);
389
            }
390
391 89
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
392
        }
393
394 799
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
395 799
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
396
397
        // Assign an SQL executor to the parser result
398 799
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
399
400 791
        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 805
    private function fixIdentificationVariableOrder($AST)
415
    {
416 805
        if (count($this->identVariableExpressions) <= 1) {
417 628
            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 170
    private function peekBeyondClosingParenthesis($resetPeek = true)
502
    {
503 170
        $token = $this->lexer->peek();
504 170
        $numUnmatched = 1;
505
506 170
        while ($numUnmatched > 0 && $token !== null) {
507 169
            switch ($token['type']) {
508 169
                case Lexer::T_OPEN_PARENTHESIS:
509 44
                    ++$numUnmatched;
510 44
                    break;
511
512 169
                case Lexer::T_CLOSE_PARENTHESIS:
513 169
                    --$numUnmatched;
514 169
                    break;
515
516
                default:
517
                    // Do nothing
518
            }
519
520 169
            $token = $this->lexer->peek();
521
        }
522
523 170
        if ($resetPeek) {
524 149
            $this->lexer->resetPeek();
525
        }
526
527 170
        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 358
    private function isMathOperator($token)
538
    {
539 358
        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 404
    private function isFunction()
548
    {
549 404
        $lookaheadType = $this->lexer->lookahead['type'];
550 404
        $peek          = $this->lexer->peek();
551
552 404
        $this->lexer->resetPeek();
553
554 404
        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 1
    private function isAggregateFunction($tokenType)
565
    {
566 1
        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 304
    private function isNextAllAnySome()
575
    {
576 304
        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 817
    private function processDeferredIdentificationVariables()
586
    {
587 817
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
588 793
            $identVariable = $deferredItem['expression'];
589
590
            // Check if IdentificationVariable exists in queryComponents
591 793
            if ( ! isset($this->queryComponents[$identVariable])) {
592 1
                $this->semanticalError(
593 1
                    "'$identVariable' is not defined.", $deferredItem['token']
594
                );
595
            }
596
597 793
            $qComp = $this->queryComponents[$identVariable];
598
599
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
600 793
            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 793
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
608 1
                $this->semanticalError(
609 793
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
610
                );
611
            }
612
        }
613 815
    }
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 601
    private function processDeferredPathExpressions()
749
    {
750 601
        foreach ($this->deferredPathExpressions as $deferredItem) {
751 601
            $pathExpression = $deferredItem['expression'];
752
753 601
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
754 601
            $class = $qComp['metadata'];
755
756 601
            if (($field = $pathExpression->field) === null) {
757 39
                $field = $pathExpression->field = $class->identifier[0];
758
            }
759
760
            // Check if field or association exists
761 601
            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 600
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
769
770 600
            if (isset($class->associationMappings[$field])) {
771 87
                $assoc = $class->associationMappings[$field];
772
773 87
                $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
774 66
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
775 87
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
776
            }
777
778
            // Validate if PathExpression is one of the expected types
779 600
            $expectedType = $pathExpression->expectedType;
780
781 600
            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 598
            $pathExpression->type = $fieldType;
811
        }
812 598
    }
813
814
    /**
815
     * @return void
816
     */
817 806
    private function processRootEntityAliasSelected()
818
    {
819 806
        if ( ! count($this->identVariableExpressions)) {
820 237
            return;
821
        }
822
823 580
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
824 580
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
825 580
                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 859
    public function QueryLanguage()
840
    {
841 859
        $statement = null;
842
843 859
        $this->lexer->moveNext();
844
845 859
        switch ($this->lexer->lookahead['type']) {
846 859
            case Lexer::T_SELECT:
847 793
                $statement = $this->SelectStatement();
848 755
                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 821
        if ($this->lexer->lookahead !== null) {
865 4
            $this->syntaxError('end of string');
866
        }
867
868 817
        return $statement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statement returns the type Doctrine\ORM\Query\AST\U...ery\AST\DeleteStatement which is incompatible with the documented return type Doctrine\ORM\Query\AST\SelectStatement.
Loading history...
869
    }
870
871
    /**
872
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
873
     *
874
     * @return \Doctrine\ORM\Query\AST\SelectStatement
875
     */
876 793
    public function SelectStatement()
877
    {
878 793
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
879
880 759
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
881 756
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
882 755
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
883 755
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
884
885 755
        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 824
    public function IdentificationVariable()
922
    {
923 824
        $this->match(Lexer::T_IDENTIFIER);
924
925 824
        $identVariable = $this->lexer->token['value'];
926
927 824
        $this->deferredIdentificationVariables[] = [
928 824
            'expression'   => $identVariable,
929 824
            'nestingLevel' => $this->nestingLevel,
930 824
            'token'        => $this->lexer->token,
931
        ];
932
933 824
        return $identVariable;
934
    }
935
936
    /**
937
     * AliasIdentificationVariable = identifier
938
     *
939
     * @return string
940
     */
941 827
    public function AliasIdentificationVariable()
942
    {
943 827
        $this->match(Lexer::T_IDENTIFIER);
944
945 827
        $aliasIdentVariable = $this->lexer->token['value'];
946 827
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
947
948 827
        if ($exists) {
949 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
950
        }
951
952 827
        return $aliasIdentVariable;
953
    }
954
955
    /**
956
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
957
     *
958
     * @return string
959
     */
960 849
    public function AbstractSchemaName()
961
    {
962 849
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
963 831
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
964
965 831
            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 843
    private function validateAbstractSchemaName($schemaName)
989
    {
990 843
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
991 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
992
        }
993 828
    }
994
995
    /**
996
     * AliasResultVariable ::= identifier
997
     *
998
     * @return string
999
     */
1000 132
    public function AliasResultVariable()
1001
    {
1002 132
        $this->match(Lexer::T_IDENTIFIER);
1003
1004 128
        $resultVariable = $this->lexer->token['value'];
1005 128
        $exists = isset($this->queryComponents[$resultVariable]);
1006
1007 128
        if ($exists) {
1008 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1009
        }
1010
1011 128
        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 611
    public function PathExpression($expectedTypes)
1077
    {
1078 611
        $identVariable = $this->IdentificationVariable();
1079 611
        $field = null;
1080
1081 611
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1082 604
            $this->match(Lexer::T_DOT);
1083 604
            $this->match(Lexer::T_IDENTIFIER);
1084
1085 604
            $field = $this->lexer->token['value'];
1086
1087 604
            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 611
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1096
1097
        // Defer PathExpression validation if requested to be deferred
1098 611
        $this->deferredPathExpressions[] = [
1099 611
            'expression'   => $pathExpr,
1100 611
            'nestingLevel' => $this->nestingLevel,
1101 611
            'token'        => $this->lexer->token,
1102
        ];
1103
1104 611
        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 519
    public function SingleValuedPathExpression()
1126
    {
1127 519
        return $this->PathExpression(
1128 519
            AST\PathExpression::TYPE_STATE_FIELD |
1129 519
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1130
        );
1131
    }
1132
1133
    /**
1134
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1135
     *
1136
     * @return \Doctrine\ORM\Query\AST\PathExpression
1137
     */
1138 204
    public function StateFieldPathExpression()
1139
    {
1140 204
        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 793
    public function SelectClause()
1169
    {
1170 793
        $isDistinct = false;
1171 793
        $this->match(Lexer::T_SELECT);
1172
1173
        // Check for DISTINCT
1174 793
        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 793
        $selectExpressions = [];
1182 793
        $selectExpressions[] = $this->SelectExpression();
1183
1184 785
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1185 300
            $this->match(Lexer::T_COMMA);
1186
1187 300
            $selectExpressions[] = $this->SelectExpression();
1188
        }
1189
1190 784
        return new AST\SelectClause($selectExpressions, $isDistinct);
1191
    }
1192
1193
    /**
1194
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1195
     *
1196
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1197
     */
1198 49
    public function SimpleSelectClause()
1199
    {
1200 49
        $isDistinct = false;
1201 49
        $this->match(Lexer::T_SELECT);
1202
1203 49
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1204
            $this->match(Lexer::T_DISTINCT);
1205
1206
            $isDistinct = true;
1207
        }
1208
1209 49
        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 784
    public function FromClause()
1315
    {
1316 784
        $this->match(Lexer::T_FROM);
1317
1318 779
        $identificationVariableDeclarations = [];
1319 779
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1320
1321 759
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1322 6
            $this->match(Lexer::T_COMMA);
1323
1324 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1325
        }
1326
1327 759
        return new AST\FromClause($identificationVariableDeclarations);
1328
    }
1329
1330
    /**
1331
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1332
     *
1333
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1334
     */
1335 49
    public function SubselectFromClause()
1336
    {
1337 49
        $this->match(Lexer::T_FROM);
1338
1339 49
        $identificationVariables = [];
1340 49
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1341
1342 48
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1343
            $this->match(Lexer::T_COMMA);
1344
1345
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1346
        }
1347
1348 48
        return new AST\SubselectFromClause($identificationVariables);
1349
    }
1350
1351
    /**
1352
     * WhereClause ::= "WHERE" ConditionalExpression
1353
     *
1354
     * @return \Doctrine\ORM\Query\AST\WhereClause
1355
     */
1356 345
    public function WhereClause()
1357
    {
1358 345
        $this->match(Lexer::T_WHERE);
1359
1360 345
        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 33
    public function GroupByClause()
1381
    {
1382 33
        $this->match(Lexer::T_GROUP);
1383 33
        $this->match(Lexer::T_BY);
1384
1385 33
        $groupByItems = [$this->GroupByItem()];
1386
1387 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1388 8
            $this->match(Lexer::T_COMMA);
1389
1390 8
            $groupByItems[] = $this->GroupByItem();
1391
        }
1392
1393 32
        return new AST\GroupByClause($groupByItems);
1394
    }
1395
1396
    /**
1397
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1398
     *
1399
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1400
     */
1401 182
    public function OrderByClause()
1402
    {
1403 182
        $this->match(Lexer::T_ORDER);
1404 182
        $this->match(Lexer::T_BY);
1405
1406 182
        $orderByItems = [];
1407 182
        $orderByItems[] = $this->OrderByItem();
1408
1409 182
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1410 15
            $this->match(Lexer::T_COMMA);
1411
1412 15
            $orderByItems[] = $this->OrderByItem();
1413
        }
1414
1415 182
        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 49
    public function Subselect()
1424
    {
1425
        // Increase query nesting level
1426 49
        $this->nestingLevel++;
1427
1428 49
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1429
1430 48
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1431 48
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1432 48
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1433 48
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1434
1435
        // Decrease query nesting level
1436 48
        $this->nestingLevel--;
1437
1438 48
        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 33
    public function GroupByItem()
1463
    {
1464
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1465 33
        $glimpse = $this->lexer->glimpse();
1466
1467 33
        if ($glimpse['type'] === Lexer::T_DOT) {
1468 14
            return $this->SingleValuedPathExpression();
1469
        }
1470
1471
        // Still need to decide between IdentificationVariable or ResultVariable
1472 19
        $lookaheadValue = $this->lexer->lookahead['value'];
1473
1474 19
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1475 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1476
        }
1477
1478 18
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1479 16
            ? $this->IdentificationVariable()
1480 18
            : $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 182
    public function OrderByItem()
1492
    {
1493 182
        $this->lexer->peek(); // lookahead => '.'
1494 182
        $this->lexer->peek(); // lookahead => token after '.'
1495
1496 182
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1497
1498 182
        $this->lexer->resetPeek();
1499
1500 182
        $glimpse = $this->lexer->glimpse();
1501
1502
        switch (true) {
1503 182
            case ($this->isFunction()):
1504 2
                $expr = $this->FunctionDeclaration();
1505 2
                break;
1506
1507 180
            case ($this->isMathOperator($peek)):
1508 25
                $expr = $this->SimpleArithmeticExpression();
1509 25
                break;
1510
1511 156
            case ($glimpse['type'] === Lexer::T_DOT):
1512 141
                $expr = $this->SingleValuedPathExpression();
1513 141
                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 182
        $type = 'ASC';
1525 182
        $item = new AST\OrderByItem($expr);
1526
1527
        switch (true) {
1528 182
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1529 95
                $this->match(Lexer::T_DESC);
1530 95
                $type = 'DESC';
1531 95
                break;
1532
1533 154
            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 182
        $item->type = $type;
1542
1543 182
        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 781
    public function IdentificationVariableDeclaration()
1582
    {
1583 781
        $joins                    = [];
1584 781
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1585 764
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1586 8
            ? $this->IndexBy()
1587 764
            : null;
1588
1589 764
        $rangeVariableDeclaration->isRoot = true;
1590
1591
        while (
1592 764
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1593 764
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1594 764
            $this->lexer->isNextToken(Lexer::T_JOIN)
1595
        ) {
1596 281
            $joins[] = $this->Join();
1597
        }
1598
1599 761
        return new AST\IdentificationVariableDeclaration(
1600 761
            $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 49
    public function SubselectIdentificationVariableDeclaration()
1622
    {
1623
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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 49
        return $this->IdentificationVariableDeclaration();
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 781
    public function RangeVariableDeclaration()
1727
    {
1728 781
        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 780
        $abstractSchemaName = $this->AbstractSchemaName();
1733
1734 779
        $this->validateAbstractSchemaName($abstractSchemaName);
1735
1736 764
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1737 6
            $this->match(Lexer::T_AS);
1738
        }
1739
1740 764
        $token = $this->lexer->lookahead;
1741 764
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1742 764
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1743
1744
        // Building queryComponent
1745
        $queryComponent = [
1746 764
            'metadata'     => $classMetadata,
1747
            'parent'       => null,
1748
            'relation'     => null,
1749
            'map'          => null,
1750 764
            'nestingLevel' => $this->nestingLevel,
1751 764
            'token'        => $token
1752
        ];
1753
1754 764
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1755
1756 764
        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);
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 162
    public function ScalarExpression()
1936
    {
1937 162
        $lookahead = $this->lexer->lookahead['type'];
1938 162
        $peek      = $this->lexer->glimpse();
1939
1940
        switch (true) {
1941 162
            case ($lookahead === Lexer::T_INTEGER):
1942 159
            case ($lookahead === Lexer::T_FLOAT):
1943
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1944 159
            case ($lookahead === Lexer::T_MINUS):
1945 159
            case ($lookahead === Lexer::T_PLUS):
1946 17
                return $this->SimpleArithmeticExpression();
1947
1948 159
            case ($lookahead === Lexer::T_STRING):
1949 13
                return $this->StringPrimary();
1950
1951 157
            case ($lookahead === Lexer::T_TRUE):
1952 157
            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 157
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
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 157
            case ($lookahead === Lexer::T_CASE):
1967 153
            case ($lookahead === Lexer::T_COALESCE):
1968 153
            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 153
            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 150
            case ($this->isFunction()):
1978 27
                $this->lexer->peek(); // "("
1979
1980
                switch (true) {
1981 27
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1982
                        // SUM(u.id) + COUNT(u.id)
1983 7
                        return $this->SimpleArithmeticExpression();
1984
1985
                    default:
1986
                        // IDENTITY(u)
1987 22
                        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 793
    public function SelectExpression()
2179
    {
2180 793
        $expression    = null;
2181 793
        $identVariable = null;
2182 793
        $peek          = $this->lexer->glimpse();
2183 793
        $lookaheadType = $this->lexer->lookahead['type'];
2184
2185
        switch (true) {
2186
            // ScalarExpression (u.name)
2187 793
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2188 103
                $expression = $this->ScalarExpression();
2189 103
                break;
2190
2191
            // IdentificationVariable (u)
2192 733
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2193 606
                $expression = $identVariable = $this->IdentificationVariable();
2194 606
                break;
2195
2196
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2197 190
            case ($lookaheadType === Lexer::T_CASE):
2198 185
            case ($lookaheadType === Lexer::T_COALESCE):
2199 183
            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 181
            case ($this->isFunction()):
2205 101
                $this->lexer->peek(); // "("
2206
2207
                switch (true) {
2208 101
                    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 99
                        $expression = $this->FunctionDeclaration();
2216 99
                        break;
2217
                }
2218
2219 101
                break;
2220
2221
            // PartialObjectExpression (PARTIAL u.{id, name})
2222 81
            case ($lookaheadType === Lexer::T_PARTIAL):
2223 11
                $expression    = $this->PartialObjectExpression();
2224 11
                $identVariable = $expression->identificationVariable;
2225 11
                break;
2226
2227
            // Subselect
2228 70
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2229 23
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2230 23
                $expression = $this->Subselect();
2231 23
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2232 23
                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))
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
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]
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
2258 790
        $mustHaveAliasResultVariable = false;
2259
2260 790
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2261 123
            $this->match(Lexer::T_AS);
2262
2263 123
            $mustHaveAliasResultVariable = true;
2264
        }
2265
2266 790
        $hiddenAliasResultVariable = false;
2267
2268 790
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2269 10
            $this->match(Lexer::T_HIDDEN);
2270
2271 10
            $hiddenAliasResultVariable = true;
2272
        }
2273
2274 790
        $aliasResultVariable = null;
2275
2276 790
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
0 ignored issues
show
introduced by
The condition $mustHaveAliasResultVariable is always true.
Loading history...
2277 132
            $token = $this->lexer->lookahead;
2278 132
            $aliasResultVariable = $this->AliasResultVariable();
2279
2280
            // Include AliasResultVariable in query components.
2281 127
            $this->queryComponents[$aliasResultVariable] = [
2282 127
                'resultVariable' => $expression,
2283 127
                'nestingLevel'   => $this->nestingLevel,
2284 127
                'token'          => $token,
2285
            ];
2286
        }
2287
2288
        // AST
2289
2290 785
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2291
2292 785
        if ($identVariable) {
2293 614
            $this->identVariableExpressions[$identVariable] = $expr;
2294
        }
2295
2296 785
        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 49
    public function SimpleSelectExpression()
2308
    {
2309 49
        $peek = $this->lexer->glimpse();
2310
2311 49
        switch ($this->lexer->lookahead['type']) {
2312 49
            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 31
            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 28
        $this->lexer->peek();
2361
2362 28
        $expression = $this->ScalarExpression();
2363 28
        $expr       = new AST\SimpleSelectExpression($expression);
2364
2365 28
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2366 1
            $this->match(Lexer::T_AS);
2367
        }
2368
2369 28
        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 28
        return $expr;
2383
    }
2384
2385
    /**
2386
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2387
     *
2388
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2389
     */
2390 388
    public function ConditionalExpression()
2391
    {
2392 388
        $conditionalTerms = [];
2393 388
        $conditionalTerms[] = $this->ConditionalTerm();
2394
2395 385
        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 385
        if (count($conditionalTerms) == 1) {
2404 377
            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 388
    public function ConditionalTerm()
2416
    {
2417 388
        $conditionalFactors = [];
2418 388
        $conditionalFactors[] = $this->ConditionalFactor();
2419
2420 385
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2421 33
            $this->match(Lexer::T_AND);
2422
2423 33
            $conditionalFactors[] = $this->ConditionalFactor();
2424
        }
2425
2426
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2427
        // if only one AST\ConditionalFactor is defined
2428 385
        if (count($conditionalFactors) == 1) {
2429 366
            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 33
        return new AST\ConditionalTerm($conditionalFactors);
2433
    }
2434
2435
    /**
2436
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2437
     *
2438
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2439
     */
2440 388
    public function ConditionalFactor()
2441
    {
2442 388
        $not = false;
2443
2444 388
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2445 6
            $this->match(Lexer::T_NOT);
2446
2447 6
            $not = true;
2448
        }
2449
2450 388
        $conditionalPrimary = $this->ConditionalPrimary();
2451
2452
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2453
        // if only one AST\ConditionalPrimary is defined
2454 385
        if ( ! $not) {
0 ignored issues
show
introduced by
The condition $not is always true.
Loading history...
2455 383
            return $conditionalPrimary;
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 388
    public function ConditionalPrimary()
2470
    {
2471 388
        $condPrimary = new AST\ConditionalPrimary;
2472
2473 388
        if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2474 379
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2475
2476 376
            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 388
    public function SimpleConditionalExpression()
2505
    {
2506 388
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2507 7
            return $this->ExistsExpression();
2508
        }
2509
2510 388
        $token      = $this->lexer->lookahead;
2511 388
        $peek       = $this->lexer->glimpse();
2512 388
        $lookahead  = $token;
2513
2514 388
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2515
            $token = $this->lexer->glimpse();
2516
        }
2517
2518 388
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2519
            // Peek beyond the matching closing parenthesis.
2520 364
            $beyond = $this->lexer->peek();
2521
2522 364
            switch ($peek['value']) {
2523 364
                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 337
                    $token = $beyond;
2539
2540 337
                    while ($token['value'] === '.') {
2541 293
                        $this->lexer->peek();
2542
2543 293
                        $token = $this->lexer->peek();
2544
                    }
2545
2546
                    // Also peek beyond a NOT if there is one.
2547 337
                    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 337
                    $lookahead = $this->lexer->peek();
2553
            }
2554
2555
            // Also peek beyond a NOT if there is one.
2556 364
            if ($lookahead['type'] === Lexer::T_NOT) {
2557 7
                $lookahead = $this->lexer->peek();
2558
            }
2559
2560 364
            $this->lexer->resetPeek();
2561
        }
2562
2563 388
        if ($token['type'] === Lexer::T_BETWEEN) {
2564 8
            return $this->BetweenExpression();
2565
        }
2566
2567 382
        if ($token['type'] === Lexer::T_LIKE) {
2568 14
            return $this->LikeExpression();
2569
        }
2570
2571 369
        if ($token['type'] === Lexer::T_IN) {
2572 35
            return $this->InExpression();
2573
        }
2574
2575 343
        if ($token['type'] === Lexer::T_INSTANCE) {
2576 17
            return $this->InstanceOfExpression();
2577
        }
2578
2579 326
        if ($token['type'] === Lexer::T_MEMBER) {
2580 7
            return $this->CollectionMemberExpression();
2581
        }
2582
2583 319
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2584 14
            return $this->NullComparisonExpression();
2585
        }
2586
2587 308
        if ($token['type'] === Lexer::T_IS  && $lookahead['type'] === Lexer::T_EMPTY) {
2588 4
            return $this->EmptyCollectionComparisonExpression();
2589
        }
2590
2591 304
        return $this->ComparisonExpression();
2592
    }
2593
2594
    /**
2595
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2596
     *
2597
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2598
     */
2599 4
    public function EmptyCollectionComparisonExpression()
2600
    {
2601 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2602 4
            $this->CollectionValuedPathExpression()
2603
        );
2604 4
        $this->match(Lexer::T_IS);
2605
2606 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2607 2
            $this->match(Lexer::T_NOT);
2608 2
            $emptyCollectionCompExpr->not = true;
2609
        }
2610
2611 4
        $this->match(Lexer::T_EMPTY);
2612
2613 4
        return $emptyCollectionCompExpr;
2614
    }
2615
2616
    /**
2617
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2618
     *
2619
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2620
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2621
     *
2622
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2623
     */
2624 7
    public function CollectionMemberExpression()
2625
    {
2626 7
        $not        = false;
2627 7
        $entityExpr = $this->EntityExpression();
2628
2629 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2630
            $this->match(Lexer::T_NOT);
2631
2632
            $not = true;
2633
        }
2634
2635 7
        $this->match(Lexer::T_MEMBER);
2636
2637 7
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2638 7
            $this->match(Lexer::T_OF);
2639
        }
2640
2641 7
        $collMemberExpr = new AST\CollectionMemberExpression(
2642 7
            $entityExpr, $this->CollectionValuedPathExpression()
2643
        );
2644 7
        $collMemberExpr->not = $not;
2645
2646 7
        return $collMemberExpr;
2647
    }
2648
2649
    /**
2650
     * Literal ::= string | char | integer | float | boolean
2651
     *
2652
     * @return \Doctrine\ORM\Query\AST\Literal
2653
     */
2654 188
    public function Literal()
2655
    {
2656 188
        switch ($this->lexer->lookahead['type']) {
2657 188
            case Lexer::T_STRING:
2658 48
                $this->match(Lexer::T_STRING);
2659
2660 48
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2661 148
            case Lexer::T_INTEGER:
2662 9
            case Lexer::T_FLOAT:
2663 140
                $this->match(
2664 140
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2665
                );
2666
2667 140
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2668 8
            case Lexer::T_TRUE:
2669 4
            case Lexer::T_FALSE:
2670 8
                $this->match(
2671 8
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2672
                );
2673
2674 8
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2675
            default:
2676
                $this->syntaxError('Literal');
2677
        }
2678
    }
2679
2680
    /**
2681
     * InParameter ::= Literal | InputParameter
2682
     *
2683
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2684
     */
2685 26
    public function InParameter()
2686
    {
2687 26
        if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2688 14
            return $this->InputParameter();
2689
        }
2690
2691 12
        return $this->Literal();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->Literal() returns the type Doctrine\ORM\Query\AST\Literal which is incompatible with the documented return type string|Doctrine\ORM\Query\AST\InputParameter.
Loading history...
2692
    }
2693
2694
    /**
2695
     * InputParameter ::= PositionalParameter | NamedParameter
2696
     *
2697
     * @return \Doctrine\ORM\Query\AST\InputParameter
2698
     */
2699 171
    public function InputParameter()
2700
    {
2701 171
        $this->match(Lexer::T_INPUT_PARAMETER);
2702
2703 171
        return new AST\InputParameter($this->lexer->token['value']);
2704
    }
2705
2706
    /**
2707
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2708
     *
2709
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2710
     */
2711 338
    public function ArithmeticExpression()
2712
    {
2713 338
        $expr = new AST\ArithmeticExpression;
2714
2715 338
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2716 19
            $peek = $this->lexer->glimpse();
2717
2718 19
            if ($peek['type'] === Lexer::T_SELECT) {
2719 7
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2720 7
                $expr->subselect = $this->Subselect();
2721 7
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2722
2723 7
                return $expr;
2724
            }
2725
        }
2726
2727 338
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2728
2729 338
        return $expr;
2730
    }
2731
2732
    /**
2733
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2734
     *
2735
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2736
     */
2737 444
    public function SimpleArithmeticExpression()
2738
    {
2739 444
        $terms = [];
2740 444
        $terms[] = $this->ArithmeticTerm();
2741
2742 444
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2743 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2744
2745 21
            $terms[] = $this->lexer->token['value'];
2746 21
            $terms[] = $this->ArithmeticTerm();
2747
        }
2748
2749
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2750
        // if only one AST\ArithmeticTerm is defined
2751 444
        if (count($terms) == 1) {
2752 439
            return $terms[0];
2753
        }
2754
2755 21
        return new AST\SimpleArithmeticExpression($terms);
2756
    }
2757
2758
    /**
2759
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2760
     *
2761
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2762
     */
2763 444
    public function ArithmeticTerm()
2764
    {
2765 444
        $factors = [];
2766 444
        $factors[] = $this->ArithmeticFactor();
2767
2768 444
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2769 53
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2770
2771 53
            $factors[] = $this->lexer->token['value'];
2772 53
            $factors[] = $this->ArithmeticFactor();
2773
        }
2774
2775
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2776
        // if only one AST\ArithmeticFactor is defined
2777 444
        if (count($factors) == 1) {
2778 416
            return $factors[0];
2779
        }
2780
2781 53
        return new AST\ArithmeticTerm($factors);
2782
    }
2783
2784
    /**
2785
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2786
     *
2787
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2788
     */
2789 444
    public function ArithmeticFactor()
2790
    {
2791 444
        $sign = null;
2792
2793 444
        if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2794 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2795 3
            $sign = $isPlus;
2796
        }
2797
2798 444
        $primary = $this->ArithmeticPrimary();
2799
2800
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2801
        // if only one AST\ArithmeticPrimary is defined
2802 444
        if ($sign === null) {
0 ignored issues
show
introduced by
The condition $sign === null is always false.
Loading history...
2803 443
            return $primary;
2804
        }
2805
2806 3
        return new AST\ArithmeticFactor($primary, $sign);
2807
    }
2808
2809
    /**
2810
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2811
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2812
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2813
     *          | InputParameter | CaseExpression
2814
     */
2815 459
    public function ArithmeticPrimary()
2816
    {
2817 459
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2818 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2819
2820 25
            $expr = $this->SimpleArithmeticExpression();
2821
2822 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2823
2824 25
            return new AST\ParenthesisExpression($expr);
2825
        }
2826
2827 459
        switch ($this->lexer->lookahead['type']) {
2828 459
            case Lexer::T_COALESCE:
2829 459
            case Lexer::T_NULLIF:
2830 459
            case Lexer::T_CASE:
2831 4
                return $this->CaseExpression();
2832
2833 459
            case Lexer::T_IDENTIFIER:
2834 429
                $peek = $this->lexer->glimpse();
2835
2836 429
                if ($peek['value'] == '(') {
2837 39
                    return $this->FunctionDeclaration();
2838
                }
2839
2840 398
                if ($peek['value'] == '.') {
2841 387
                    return $this->SingleValuedPathExpression();
2842
                }
2843
2844 46
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2845 10
                    return $this->ResultVariable();
2846
                }
2847
2848 38
                return $this->StateFieldPathExpression();
2849
2850 326
            case Lexer::T_INPUT_PARAMETER:
2851 152
                return $this->InputParameter();
2852
2853
            default:
2854 182
                $peek = $this->lexer->glimpse();
2855
2856 182
                if ($peek['value'] == '(') {
2857 18
                    return $this->FunctionDeclaration();
2858
                }
2859
2860 178
                return $this->Literal();
2861
        }
2862
    }
2863
2864
    /**
2865
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2866
     *
2867
     * @return \Doctrine\ORM\Query\AST\Subselect |
2868
     *         string
2869
     */
2870 14
    public function StringExpression()
2871
    {
2872 14
        $peek = $this->lexer->glimpse();
2873
2874
        // Subselect
2875 14
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2876
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2877
            $expr = $this->Subselect();
2878
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2879
2880
            return $expr;
2881
        }
2882
2883
        // ResultVariable (string)
2884 14
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2885 14
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2886 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...
2887
        }
2888
2889 12
        return $this->StringPrimary();
2890
    }
2891
2892
    /**
2893
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2894
     */
2895 62
    public function StringPrimary()
2896
    {
2897 62
        $lookaheadType = $this->lexer->lookahead['type'];
2898
2899
        switch ($lookaheadType) {
2900 62
            case Lexer::T_IDENTIFIER:
2901 32
                $peek = $this->lexer->glimpse();
2902
2903 32
                if ($peek['value'] == '.') {
2904 32
                    return $this->StateFieldPathExpression();
2905
                }
2906
2907 8
                if ($peek['value'] == '(') {
2908
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2909 8
                    return $this->FunctionDeclaration();
2910
                }
2911
2912
                $this->syntaxError("'.' or '('");
2913
                break;
2914
2915 43
            case Lexer::T_STRING:
2916 43
                $this->match(Lexer::T_STRING);
2917
2918 43
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2919
2920 2
            case Lexer::T_INPUT_PARAMETER:
2921 2
                return $this->InputParameter();
2922
2923
            case Lexer::T_CASE:
2924
            case Lexer::T_COALESCE:
2925
            case Lexer::T_NULLIF:
2926
                return $this->CaseExpression();
2927
        }
2928
2929
        $this->syntaxError(
2930
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2931
        );
2932
    }
2933
2934
    /**
2935
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2936
     *
2937
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2938
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2939
     */
2940 7
    public function EntityExpression()
2941
    {
2942 7
        $glimpse = $this->lexer->glimpse();
2943
2944 7
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2945 1
            return $this->SingleValuedAssociationPathExpression();
2946
        }
2947
2948 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...
2949
    }
2950
2951
    /**
2952
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2953
     *
2954
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2955
     */
2956 6
    public function SimpleEntityExpression()
2957
    {
2958 6
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2959 5
            return $this->InputParameter();
2960
        }
2961
2962 1
        return $this->StateFieldPathExpression();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->StateFieldPathExpression() returns the type Doctrine\ORM\Query\AST\PathExpression which is incompatible with the documented return type string|Doctrine\ORM\Query\AST\InputParameter.
Loading history...
2963
    }
2964
2965
    /**
2966
     * AggregateExpression ::=
2967
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2968
     *
2969
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2970
     */
2971 88
    public function AggregateExpression()
2972
    {
2973 88
        $lookaheadType = $this->lexer->lookahead['type'];
2974 88
        $isDistinct = false;
2975
2976 88
        if ( ! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM])) {
2977
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2978
        }
2979
2980 88
        $this->match($lookaheadType);
2981 88
        $functionName = $this->lexer->token['value'];
2982 88
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2983
2984 88
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2985 3
            $this->match(Lexer::T_DISTINCT);
2986 3
            $isDistinct = true;
2987
        }
2988
2989 88
        $pathExp = $this->SimpleArithmeticExpression();
2990
2991 88
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2992
2993 88
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2994
    }
2995
2996
    /**
2997
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2998
     *
2999
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
3000
     */
3001 3
    public function QuantifiedExpression()
3002
    {
3003 3
        $lookaheadType = $this->lexer->lookahead['type'];
3004 3
        $value = $this->lexer->lookahead['value'];
3005
3006 3
        if ( ! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME])) {
3007
            $this->syntaxError('ALL, ANY or SOME');
3008
        }
3009
3010 3
        $this->match($lookaheadType);
3011 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3012
3013 3
        $qExpr = new AST\QuantifiedExpression($this->Subselect());
3014 3
        $qExpr->type = $value;
3015
3016 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3017
3018 3
        return $qExpr;
3019
    }
3020
3021
    /**
3022
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
3023
     *
3024
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
3025
     */
3026 8
    public function BetweenExpression()
3027
    {
3028 8
        $not = false;
3029 8
        $arithExpr1 = $this->ArithmeticExpression();
3030
3031 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3032 3
            $this->match(Lexer::T_NOT);
3033 3
            $not = true;
3034
        }
3035
3036 8
        $this->match(Lexer::T_BETWEEN);
3037 8
        $arithExpr2 = $this->ArithmeticExpression();
3038 8
        $this->match(Lexer::T_AND);
3039 8
        $arithExpr3 = $this->ArithmeticExpression();
3040
3041 8
        $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3042 8
        $betweenExpr->not = $not;
3043
3044 8
        return $betweenExpr;
3045
    }
3046
3047
    /**
3048
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3049
     *
3050
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3051
     */
3052 304
    public function ComparisonExpression()
3053
    {
3054 304
        $this->lexer->glimpse();
3055
3056 304
        $leftExpr  = $this->ArithmeticExpression();
3057 304
        $operator  = $this->ComparisonOperator();
3058 304
        $rightExpr = ($this->isNextAllAnySome())
3059 3
            ? $this->QuantifiedExpression()
3060 304
            : $this->ArithmeticExpression();
3061
3062 302
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3063
    }
3064
3065
    /**
3066
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3067
     *
3068
     * @return \Doctrine\ORM\Query\AST\InExpression
3069
     */
3070 35
    public function InExpression()
3071
    {
3072 35
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3073
3074 35
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3075 6
            $this->match(Lexer::T_NOT);
3076 6
            $inExpression->not = true;
3077
        }
3078
3079 35
        $this->match(Lexer::T_IN);
3080 35
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3081
3082 35
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3083 9
            $inExpression->subselect = $this->Subselect();
3084
        } else {
3085 26
            $literals = [];
3086 26
            $literals[] = $this->InParameter();
3087
3088 26
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3089 16
                $this->match(Lexer::T_COMMA);
3090 16
                $literals[] = $this->InParameter();
3091
            }
3092
3093 26
            $inExpression->literals = $literals;
3094
        }
3095
3096 34
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3097
3098 34
        return $inExpression;
3099
    }
3100
3101
    /**
3102
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3103
     *
3104
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3105
     */
3106 17
    public function InstanceOfExpression()
3107
    {
3108 17
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3109
3110 17
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3111 1
            $this->match(Lexer::T_NOT);
3112 1
            $instanceOfExpression->not = true;
3113
        }
3114
3115 17
        $this->match(Lexer::T_INSTANCE);
3116 17
        $this->match(Lexer::T_OF);
3117
3118 17
        $exprValues = [];
3119
3120 17
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3121 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3122
3123 2
            $exprValues[] = $this->InstanceOfParameter();
3124
3125 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3126 2
                $this->match(Lexer::T_COMMA);
3127
3128 2
                $exprValues[] = $this->InstanceOfParameter();
3129
            }
3130
3131 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3132
3133 2
            $instanceOfExpression->value = $exprValues;
3134
3135 2
            return $instanceOfExpression;
3136
        }
3137
3138 15
        $exprValues[] = $this->InstanceOfParameter();
3139
3140 15
        $instanceOfExpression->value = $exprValues;
3141
3142 15
        return $instanceOfExpression;
3143
    }
3144
3145
    /**
3146
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3147
     *
3148
     * @return mixed
3149
     */
3150 17
    public function InstanceOfParameter()
3151
    {
3152 17
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3153 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3154
3155 6
            return new AST\InputParameter($this->lexer->token['value']);
3156
        }
3157
3158 11
        $abstractSchemaName = $this->AbstractSchemaName();
3159
3160 11
        $this->validateAbstractSchemaName($abstractSchemaName);
3161
3162 11
        return $abstractSchemaName;
3163
    }
3164
3165
    /**
3166
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3167
     *
3168
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3169
     */
3170 14
    public function LikeExpression()
3171
    {
3172 14
        $stringExpr = $this->StringExpression();
3173 14
        $not = false;
3174
3175 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3176 3
            $this->match(Lexer::T_NOT);
3177 3
            $not = true;
3178
        }
3179
3180 14
        $this->match(Lexer::T_LIKE);
3181
3182 14
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3183 4
            $this->match(Lexer::T_INPUT_PARAMETER);
3184 4
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3185
        } else {
3186 11
            $stringPattern = $this->StringPrimary();
3187
        }
3188
3189 14
        $escapeChar = null;
3190
3191 14
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3192 2
            $this->match(Lexer::T_ESCAPE);
3193 2
            $this->match(Lexer::T_STRING);
3194
3195 2
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3196
        }
3197
3198 14
        $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
3199 14
        $likeExpr->not = $not;
3200
3201 14
        return $likeExpr;
3202
    }
3203
3204
    /**
3205
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3206
     *
3207
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3208
     */
3209 14
    public function NullComparisonExpression()
3210
    {
3211
        switch (true) {
3212 14
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3213
                $this->match(Lexer::T_INPUT_PARAMETER);
3214
3215
                $expr = new AST\InputParameter($this->lexer->token['value']);
3216
                break;
3217
3218 14
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3219 1
                $expr = $this->NullIfExpression();
3220 1
                break;
3221
3222 14
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3223 1
                $expr = $this->CoalesceExpression();
3224 1
                break;
3225
3226 14
            case $this->isFunction():
3227 2
                $expr = $this->FunctionDeclaration();
3228 2
                break;
3229
3230
            default:
3231
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3232 13
                $glimpse = $this->lexer->glimpse();
3233
3234 13
                if ($glimpse['type'] === Lexer::T_DOT) {
3235 9
                    $expr = $this->SingleValuedPathExpression();
3236
3237
                    // Leave switch statement
3238 9
                    break;
3239
                }
3240
3241 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3242
3243
                // Validate existing component
3244 4
                if ( ! isset($this->queryComponents[$lookaheadValue])) {
3245
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3246
                }
3247
3248
                // Validate SingleValuedPathExpression (ie.: "product")
3249 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3250 1
                    $expr = $this->SingleValuedPathExpression();
3251 1
                    break;
3252
                }
3253
3254
                // Validating ResultVariable
3255 3
                if ( ! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3256
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3257
                }
3258
3259 3
                $expr = $this->ResultVariable();
3260 3
                break;
3261
        }
3262
3263 14
        $nullCompExpr = new AST\NullComparisonExpression($expr);
3264
3265 14
        $this->match(Lexer::T_IS);
3266
3267 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3268 5
            $this->match(Lexer::T_NOT);
3269
3270 5
            $nullCompExpr->not = true;
3271
        }
3272
3273 14
        $this->match(Lexer::T_NULL);
3274
3275 14
        return $nullCompExpr;
3276
    }
3277
3278
    /**
3279
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3280
     *
3281
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
3282
     */
3283 7
    public function ExistsExpression()
3284
    {
3285 7
        $not = false;
3286
3287 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3288
            $this->match(Lexer::T_NOT);
3289
            $not = true;
3290
        }
3291
3292 7
        $this->match(Lexer::T_EXISTS);
3293 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3294
3295 7
        $existsExpression = new AST\ExistsExpression($this->Subselect());
3296 7
        $existsExpression->not = $not;
3297
3298 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3299
3300 7
        return $existsExpression;
3301
    }
3302
3303
    /**
3304
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3305
     *
3306
     * @return string
3307
     */
3308 304
    public function ComparisonOperator()
3309
    {
3310 304
        switch ($this->lexer->lookahead['value']) {
3311 304
            case '=':
3312 253
                $this->match(Lexer::T_EQUALS);
3313
3314 253
                return '=';
3315
3316 62
            case '<':
3317 17
                $this->match(Lexer::T_LOWER_THAN);
3318 17
                $operator = '<';
3319
3320 17
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3321 5
                    $this->match(Lexer::T_EQUALS);
3322 5
                    $operator .= '=';
3323 12
                } else if ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3324 3
                    $this->match(Lexer::T_GREATER_THAN);
3325 3
                    $operator .= '>';
3326
                }
3327
3328 17
                return $operator;
3329
3330 53
            case '>':
3331 47
                $this->match(Lexer::T_GREATER_THAN);
3332 47
                $operator = '>';
3333
3334 47
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3335 6
                    $this->match(Lexer::T_EQUALS);
3336 6
                    $operator .= '=';
3337
                }
3338
3339 47
                return $operator;
3340
3341 6
            case '!':
3342 6
                $this->match(Lexer::T_NEGATE);
3343 6
                $this->match(Lexer::T_EQUALS);
3344
3345 6
                return '<>';
3346
3347
            default:
3348
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
3349
        }
3350
    }
3351
3352
    /**
3353
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3354
     *
3355
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3356
     */
3357 155
    public function FunctionDeclaration()
3358
    {
3359 155
        $token = $this->lexer->lookahead;
3360 155
        $funcName = strtolower($token['value']);
3361
3362 155
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3363
3364
        // Check for custom functions functions first!
3365
        switch (true) {
3366 155
            case $customFunctionDeclaration !== null:
3367 4
                return $customFunctionDeclaration;
3368
3369 151
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3370 30
                return $this->FunctionsReturningStrings();
3371
3372 126
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3373 109
                return $this->FunctionsReturningNumerics();
3374
3375 18
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3376 18
                return $this->FunctionsReturningDatetime();
3377
3378
            default:
3379
                $this->syntaxError('known function', $token);
3380
        }
3381
    }
3382
3383
    /**
3384
     * Helper function for FunctionDeclaration grammar rule.
3385
     *
3386
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3387
     */
3388 155
    private function CustomFunctionDeclaration()
3389
    {
3390 155
        $token = $this->lexer->lookahead;
3391 155
        $funcName = strtolower($token['value']);
3392
3393
        // Check for custom functions afterwards
3394 155
        $config = $this->em->getConfiguration();
3395
3396
        switch (true) {
3397 155
            case ($config->getCustomStringFunction($funcName) !== null):
3398 3
                return $this->CustomFunctionsReturningStrings();
3399
3400 153
            case ($config->getCustomNumericFunction($funcName) !== null):
3401 2
                return $this->CustomFunctionsReturningNumerics();
3402
3403 151
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3404
                return $this->CustomFunctionsReturningDatetime();
3405
3406
            default:
3407 151
                return null;
3408
        }
3409
    }
3410
3411
    /**
3412
     * FunctionsReturningNumerics ::=
3413
     *      "LENGTH" "(" StringPrimary ")" |
3414
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3415
     *      "ABS" "(" SimpleArithmeticExpression ")" |
3416
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
3417
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3418
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
3419
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3420
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3421
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3422
     *
3423
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3424
     */
3425 109
    public function FunctionsReturningNumerics()
3426
    {
3427 109
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3428 109
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3429
3430 109
        $function = new $funcClass($funcNameLower);
3431 109
        $function->parse($this);
3432
3433 109
        return $function;
3434
    }
3435
3436
    /**
3437
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3438
     */
3439 2
    public function CustomFunctionsReturningNumerics()
3440
    {
3441
        // getCustomNumericFunction is case-insensitive
3442 2
        $functionName  = strtolower($this->lexer->lookahead['value']);
3443 2
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3444
3445 2
        $function = is_string($functionClass)
3446 1
            ? new $functionClass($functionName)
3447 2
            : call_user_func($functionClass, $functionName);
3448
3449 2
        $function->parse($this);
3450
3451 2
        return $function;
3452
    }
3453
3454
    /**
3455
     * FunctionsReturningDateTime ::=
3456
     *     "CURRENT_DATE" |
3457
     *     "CURRENT_TIME" |
3458
     *     "CURRENT_TIMESTAMP" |
3459
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3460
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3461
     *
3462
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3463
     */
3464 18
    public function FunctionsReturningDatetime()
3465
    {
3466 18
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3467 18
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3468
3469 18
        $function = new $funcClass($funcNameLower);
3470 18
        $function->parse($this);
3471
3472 18
        return $function;
3473
    }
3474
3475
    /**
3476
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3477
     */
3478
    public function CustomFunctionsReturningDatetime()
3479
    {
3480
        // getCustomDatetimeFunction is case-insensitive
3481
        $functionName  = $this->lexer->lookahead['value'];
3482
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3483
3484
        $function = is_string($functionClass)
3485
            ? new $functionClass($functionName)
3486
            : call_user_func($functionClass, $functionName);
3487
3488
        $function->parse($this);
3489
3490
        return $function;
3491
    }
3492
3493
    /**
3494
     * FunctionsReturningStrings ::=
3495
     *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3496
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3497
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3498
     *   "LOWER" "(" StringPrimary ")" |
3499
     *   "UPPER" "(" StringPrimary ")" |
3500
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3501
     *
3502
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3503
     */
3504 30
    public function FunctionsReturningStrings()
3505
    {
3506 30
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3507 30
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3508
3509 30
        $function = new $funcClass($funcNameLower);
3510 30
        $function->parse($this);
3511
3512 30
        return $function;
3513
    }
3514
3515
    /**
3516
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3517
     */
3518 3
    public function CustomFunctionsReturningStrings()
3519
    {
3520
        // getCustomStringFunction is case-insensitive
3521 3
        $functionName  = $this->lexer->lookahead['value'];
3522 3
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3523
3524 3
        $function = is_string($functionClass)
3525 2
            ? new $functionClass($functionName)
3526 3
            : call_user_func($functionClass, $functionName);
3527
3528 3
        $function->parse($this);
3529
3530 3
        return $function;
3531
    }
3532
}
3533