Failed Conditions
Push — master ( 2ade86...13f838 )
by Jonathan
18s
created

lib/Doctrine/ORM/Query/Parser.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
26
/**
27
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
28
 * Parses a DQL query, reports any errors in it, and generates an AST.
29
 *
30
 * @since   2.0
31
 * @author  Guilherme Blanco <[email protected]>
32
 * @author  Jonathan Wage <[email protected]>
33
 * @author  Roman Borschel <[email protected]>
34
 * @author  Janne Vanhala <[email protected]>
35
 * @author  Fabio B. Silva <[email protected]>
36
 */
37
class Parser
38
{
39
    /**
40
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
41
     *
42
     * @var array
43
     */
44
    private static $_STRING_FUNCTIONS = [
45
        'concat'    => Functions\ConcatFunction::class,
46
        'substring' => Functions\SubstringFunction::class,
47
        'trim'      => Functions\TrimFunction::class,
48
        'lower'     => Functions\LowerFunction::class,
49
        'upper'     => Functions\UpperFunction::class,
50
        'identity'  => Functions\IdentityFunction::class,
51
    ];
52
53
    /**
54
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
55
     *
56
     * @var array
57
     */
58
    private static $_NUMERIC_FUNCTIONS = [
59
        'length'    => Functions\LengthFunction::class,
60
        'locate'    => Functions\LocateFunction::class,
61
        'abs'       => Functions\AbsFunction::class,
62
        'sqrt'      => Functions\SqrtFunction::class,
63
        'mod'       => Functions\ModFunction::class,
64
        'size'      => Functions\SizeFunction::class,
65
        'date_diff' => Functions\DateDiffFunction::class,
66
        'bit_and'   => Functions\BitAndFunction::class,
67
        'bit_or'    => Functions\BitOrFunction::class,
68
69
        // Aggregate functions
70
        'min'       => Functions\MinFunction::class,
71
        'max'       => Functions\MaxFunction::class,
72
        'avg'       => Functions\AvgFunction::class,
73
        'sum'       => Functions\SumFunction::class,
74
        'count'     => Functions\CountFunction::class,
75
    ];
76
77
    /**
78
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
79
     *
80
     * @var array
81
     */
82
    private static $_DATETIME_FUNCTIONS = [
83
        'current_date'      => Functions\CurrentDateFunction::class,
84
        'current_time'      => Functions\CurrentTimeFunction::class,
85
        'current_timestamp' => Functions\CurrentTimestampFunction::class,
86
        'date_add'          => Functions\DateAddFunction::class,
87
        'date_sub'          => Functions\DateSubFunction::class,
88
    ];
89
90
    /*
91
     * Expressions that were encountered during parsing of identifiers and expressions
92
     * and still need to be validated.
93
     */
94
95
    /**
96
     * @var array
97
     */
98
    private $deferredIdentificationVariables = [];
99
100
    /**
101
     * @var array
102
     */
103
    private $deferredPartialObjectExpressions = [];
104
105
    /**
106
     * @var array
107
     */
108
    private $deferredPathExpressions = [];
109
110
    /**
111
     * @var array
112
     */
113
    private $deferredResultVariables = [];
114
115
    /**
116
     * @var array
117
     */
118
    private $deferredNewObjectExpressions = [];
119
120
    /**
121
     * The lexer.
122
     *
123
     * @var \Doctrine\ORM\Query\Lexer
124
     */
125
    private $lexer;
126
127
    /**
128
     * The parser result.
129
     *
130
     * @var \Doctrine\ORM\Query\ParserResult
131
     */
132
    private $parserResult;
133
134
    /**
135
     * The EntityManager.
136
     *
137
     * @var \Doctrine\ORM\EntityManager
138
     */
139
    private $em;
140
141
    /**
142
     * The Query to parse.
143
     *
144
     * @var Query
145
     */
146
    private $query;
147
148
    /**
149
     * Map of declared query components in the parsed query.
150
     *
151
     * @var array
152
     */
153
    private $queryComponents = [];
154
155
    /**
156
     * Keeps the nesting level of defined ResultVariables.
157
     *
158
     * @var integer
159
     */
160
    private $nestingLevel = 0;
161
162
    /**
163
     * Any additional custom tree walkers that modify the AST.
164
     *
165
     * @var array
166
     */
167
    private $customTreeWalkers = [];
168
169
    /**
170
     * The custom last tree walker, if any, that is responsible for producing the output.
171
     *
172
     * @var TreeWalker
173
     */
174
    private $customOutputWalker;
175
176
    /**
177
     * @var array
178
     */
179
    private $identVariableExpressions = [];
180
181
    /**
182
     * Creates a new query parser object.
183
     *
184
     * @param Query $query The Query to parse.
185
     */
186 839
    public function __construct(Query $query)
187
    {
188 839
        $this->query        = $query;
189 839
        $this->em           = $query->getEntityManager();
190 839
        $this->lexer        = new Lexer($query->getDQL());
191 839
        $this->parserResult = new ParserResult();
192 839
    }
193
194
    /**
195
     * Sets a custom tree walker that produces output.
196
     * This tree walker will be run last over the AST, after any other walkers.
197
     *
198
     * @param string $className
199
     *
200
     * @return void
201
     */
202 127
    public function setCustomOutputTreeWalker($className)
203
    {
204 127
        $this->customOutputWalker = $className;
205 127
    }
206
207
    /**
208
     * Adds a custom tree walker for modifying the AST.
209
     *
210
     * @param string $className
211
     *
212
     * @return void
213
     */
214
    public function addCustomTreeWalker($className)
215
    {
216
        $this->customTreeWalkers[] = $className;
217
    }
218
219
    /**
220
     * Gets the lexer used by the parser.
221
     *
222
     * @return \Doctrine\ORM\Query\Lexer
223
     */
224 28
    public function getLexer()
225
    {
226 28
        return $this->lexer;
227
    }
228
229
    /**
230
     * Gets the ParserResult that is being filled with information during parsing.
231
     *
232
     * @return \Doctrine\ORM\Query\ParserResult
233
     */
234
    public function getParserResult()
235
    {
236
        return $this->parserResult;
237
    }
238
239
    /**
240
     * Gets the EntityManager used by the parser.
241
     *
242
     * @return \Doctrine\ORM\EntityManager
243
     */
244
    public function getEntityManager()
245
    {
246
        return $this->em;
247
    }
248
249
    /**
250
     * Parses and builds AST for the given Query.
251
     *
252
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
253
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
254
     *         \Doctrine\ORM\Query\AST\DeleteStatement
255
     */
256 839
    public function getAST()
257
    {
258
        // Parse & build AST
259 839
        $AST = $this->QueryLanguage();
260
261
        // Process any deferred validations of some nodes in the AST.
262
        // This also allows post-processing of the AST for modification purposes.
263 797
        $this->processDeferredIdentificationVariables();
264
265 795
        if ($this->deferredPartialObjectExpressions) {
266 11
            $this->processDeferredPartialObjectExpressions();
267
        }
268
269 793
        if ($this->deferredPathExpressions) {
270 594
            $this->processDeferredPathExpressions();
271
        }
272
273 790
        if ($this->deferredResultVariables) {
274 30
            $this->processDeferredResultVariables();
275
        }
276
277 790
        if ($this->deferredNewObjectExpressions) {
278 28
            $this->processDeferredNewObjectExpressions($AST);
279
        }
280
281 786
        $this->processRootEntityAliasSelected();
282
283
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
284 785
        $this->fixIdentificationVariableOrder($AST);
285
286 785
        return $AST;
287
    }
288
289
    /**
290
     * Attempts to match the given token with the current lookahead token.
291
     *
292
     * If they match, updates the lookahead token; otherwise raises a syntax
293
     * error.
294
     *
295
     * @param int $token The token type.
296
     *
297
     * @return void
298
     *
299
     * @throws QueryException If the tokens don't match.
300
     */
301 850
    public function match($token)
302
    {
303 850
        $lookaheadType = $this->lexer->lookahead['type'];
304
305
        // Short-circuit on first condition, usually types match
306 850
        if ($lookaheadType !== $token) {
307
            // If parameter is not identifier (1-99) must be exact match
308 21
            if ($token < Lexer::T_IDENTIFIER) {
309 3
                $this->syntaxError($this->lexer->getLiteral($token));
310
            }
311
312
            // If parameter is keyword (200+) must be exact match
313 18
            if ($token > Lexer::T_IDENTIFIER) {
314 7
                $this->syntaxError($this->lexer->getLiteral($token));
315
            }
316
317
            // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
318 11
            if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
319 8
                $this->syntaxError($this->lexer->getLiteral($token));
320
            }
321
        }
322
323 843
        $this->lexer->moveNext();
324 843
    }
325
326
    /**
327
     * Frees this parser, enabling it to be reused.
328
     *
329
     * @param boolean $deep     Whether to clean peek and reset errors.
330
     * @param integer $position Position to reset.
331
     *
332
     * @return void
333
     */
334
    public function free($deep = false, $position = 0)
335
    {
336
        // WARNING! Use this method with care. It resets the scanner!
337
        $this->lexer->resetPosition($position);
338
339
        // Deep = true cleans peek and also any previously defined errors
340
        if ($deep) {
341
            $this->lexer->resetPeek();
342
        }
343
344
        $this->lexer->token = null;
345
        $this->lexer->lookahead = null;
346
    }
347
348
    /**
349
     * Parses a query string.
350
     *
351
     * @return ParserResult
352
     */
353 839
    public function parse()
354
    {
355 839
        $AST = $this->getAST();
356
357 785
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
358 93
            $this->customTreeWalkers = $customWalkers;
359
        }
360
361 785
        if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
362 79
            $this->customOutputWalker = $customOutputWalker;
363
        }
364
365
        // Run any custom tree walkers over the AST
366 785
        if ($this->customTreeWalkers) {
367 92
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
368
369 92
            foreach ($this->customTreeWalkers as $walker) {
370 92
                $treeWalkerChain->addTreeWalker($walker);
371
            }
372
373
            switch (true) {
374 92
                case ($AST instanceof AST\UpdateStatement):
375
                    $treeWalkerChain->walkUpdateStatement($AST);
376
                    break;
377
378 92
                case ($AST instanceof AST\DeleteStatement):
379
                    $treeWalkerChain->walkDeleteStatement($AST);
380
                    break;
381
382 92
                case ($AST instanceof AST\SelectStatement):
383
                default:
384 92
                    $treeWalkerChain->walkSelectStatement($AST);
385
            }
386
387 86
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
388
        }
389
390 779
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
391 779
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
392
393
        // Assign an SQL executor to the parser result
394 779
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
395
396 771
        return $this->parserResult;
397
    }
398
399
    /**
400
     * Fixes order of identification variables.
401
     *
402
     * They have to appear in the select clause in the same order as the
403
     * declarations (from ... x join ... y join ... z ...) appear in the query
404
     * as the hydration process relies on that order for proper operation.
405
     *
406
     * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
407
     *
408
     * @return void
409
     */
410 785
    private function fixIdentificationVariableOrder($AST)
411
    {
412 785
        if (count($this->identVariableExpressions) <= 1) {
413 608
            return;
414
        }
415
416 182
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
417 182
            if ( ! isset($this->identVariableExpressions[$dqlAlias])) {
418 8
                continue;
419
            }
420
421 182
            $expr = $this->identVariableExpressions[$dqlAlias];
422 182
            $key  = array_search($expr, $AST->selectClause->selectExpressions);
423
424 182
            unset($AST->selectClause->selectExpressions[$key]);
425
426 182
            $AST->selectClause->selectExpressions[] = $expr;
427
        }
428 182
    }
429
430
    /**
431
     * Generates a new syntax error.
432
     *
433
     * @param string     $expected Expected string.
434
     * @param array|null $token    Got token.
435
     *
436
     * @return void
437
     *
438
     * @throws \Doctrine\ORM\Query\QueryException
439
     */
440 18
    public function syntaxError($expected = '', $token = null)
441
    {
442 18
        if ($token === null) {
443 15
            $token = $this->lexer->lookahead;
444
        }
445
446 18
        $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
447
448 18
        $message  = "line 0, col {$tokenPos}: Error: ";
449 18
        $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected ';
450 18
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'";
451
452 18
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
453
    }
454
455
    /**
456
     * Generates a new semantical error.
457
     *
458
     * @param string     $message Optional message.
459
     * @param array|null $token   Optional token.
460
     *
461
     * @return void
462
     *
463
     * @throws \Doctrine\ORM\Query\QueryException
464
     */
465 35
    public function semanticalError($message = '', $token = null)
466
    {
467 35
        if ($token === null) {
468 2
            $token = $this->lexer->lookahead;
469
        }
470
471
        // Minimum exposed chars ahead of token
472 35
        $distance = 12;
473
474
        // Find a position of a final word to display in error string
475 35
        $dql    = $this->query->getDQL();
476 35
        $length = strlen($dql);
477 35
        $pos    = $token['position'] + $distance;
478 35
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
479 35
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
480
481 35
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
482 35
        $tokenStr = substr($dql, $token['position'], $length);
483
484
        // Building informative message
485 35
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
486
487 35
        throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL()));
488
    }
489
490
    /**
491
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
492
     *
493
     * @param boolean $resetPeek Reset peek after finding the closing parenthesis.
494
     *
495
     * @return array
496
     */
497 157
    private function peekBeyondClosingParenthesis($resetPeek = true)
498
    {
499 157
        $token = $this->lexer->peek();
500 157
        $numUnmatched = 1;
501
502 157
        while ($numUnmatched > 0 && $token !== null) {
503 156
            switch ($token['type']) {
504 156
                case Lexer::T_OPEN_PARENTHESIS:
505 33
                    ++$numUnmatched;
506 33
                    break;
507
508 156
                case Lexer::T_CLOSE_PARENTHESIS:
509 156
                    --$numUnmatched;
510 156
                    break;
511
512
                default:
513
                    // Do nothing
514
            }
515
516 156
            $token = $this->lexer->peek();
517
        }
518
519 157
        if ($resetPeek) {
520 136
            $this->lexer->resetPeek();
521
        }
522
523 157
        return $token;
524
    }
525
526
    /**
527
     * Checks if the given token indicates a mathematical operator.
528
     *
529
     * @param array $token
530
     *
531
     * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise.
532
     */
533 345
    private function isMathOperator($token)
534
    {
535 345
        return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
536
    }
537
538
    /**
539
     * Checks if the next-next (after lookahead) token starts a function.
540
     *
541
     * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
542
     */
543 390
    private function isFunction()
544
    {
545 390
        $lookaheadType = $this->lexer->lookahead['type'];
546 390
        $peek          = $this->lexer->peek();
547
548 390
        $this->lexer->resetPeek();
549
550 390
        return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
551
    }
552
553
    /**
554
     * Checks whether the given token type indicates an aggregate function.
555
     *
556
     * @param int $tokenType
557
     *
558
     * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
559
     */
560 1
    private function isAggregateFunction($tokenType)
561
    {
562 1
        return in_array($tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT]);
563
    }
564
565
    /**
566
     * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
567
     *
568
     * @return boolean
569
     */
570 299
    private function isNextAllAnySome()
571
    {
572 299
        return in_array($this->lexer->lookahead['type'], [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME]);
573
    }
574
575
    /**
576
     * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
577
     * It must exist in query components list.
578
     *
579
     * @return void
580
     */
581 797 View Code Duplication
    private function processDeferredIdentificationVariables()
582
    {
583 797
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
584 785
            $identVariable = $deferredItem['expression'];
585
586
            // Check if IdentificationVariable exists in queryComponents
587 785
            if ( ! isset($this->queryComponents[$identVariable])) {
588 1
                $this->semanticalError(
589 1
                    "'$identVariable' is not defined.", $deferredItem['token']
590
                );
591
            }
592
593 785
            $qComp = $this->queryComponents[$identVariable];
594
595
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
596 785
            if ( ! isset($qComp['metadata'])) {
597
                $this->semanticalError(
598
                    "'$identVariable' does not point to a Class.", $deferredItem['token']
599
                );
600
            }
601
602
            // Validate if identification variable nesting level is lower or equal than the current one
603 785
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
604 1
                $this->semanticalError(
605 785
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
606
                );
607
            }
608
        }
609 795
    }
610
611
    /**
612
     * Validates that the given <tt>NewObjectExpression</tt>.
613
     *
614
     * @param \Doctrine\ORM\Query\AST\SelectClause $AST
615
     *
616
     * @return void
617
     */
618 28
    private function processDeferredNewObjectExpressions($AST)
619
    {
620 28
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
621 28
            $expression     = $deferredItem['expression'];
622 28
            $token          = $deferredItem['token'];
623 28
            $className      = $expression->className;
624 28
            $args           = $expression->args;
625 28
            $fromClassName  = isset($AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName)
626 28
                ? $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName
627 28
                : null;
628
629
            // If the namespace is not given then assumes the first FROM entity namespace
630 28
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
631 11
                $namespace  = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
632 11
                $fqcn       = $namespace . '\\' . $className;
633
634 11
                if (class_exists($fqcn)) {
635 11
                    $expression->className  = $fqcn;
636 11
                    $className              = $fqcn;
637
                }
638
            }
639
640 28
            if ( ! class_exists($className)) {
641 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
642
            }
643
644 27
            $class = new \ReflectionClass($className);
645
646 27
            if ( ! $class->isInstantiable()) {
647 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
648
            }
649
650 26
            if ($class->getConstructor() === null) {
651 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
652
            }
653
654 25
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
655 25
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
656
            }
657
        }
658 24
    }
659
660
    /**
661
     * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
662
     * It must exist in query components list.
663
     *
664
     * @return void
665
     */
666 11
    private function processDeferredPartialObjectExpressions()
667
    {
668 11
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
669 11
            $expr = $deferredItem['expression'];
670 11
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
671
672 11
            foreach ($expr->partialFieldSet as $field) {
673 11
                if (isset($class->fieldMappings[$field])) {
674 10
                    continue;
675
                }
676
677 3
                if (isset($class->associationMappings[$field]) &&
678 3
                    $class->associationMappings[$field]['isOwningSide'] &&
679 3
                    $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) {
680 2
                    continue;
681
                }
682
683 1
                $this->semanticalError(
684 1
                    "There is no mapped field named '$field' on class " . $class->name . ".", $deferredItem['token']
685
                );
686
            }
687
688 10
            if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
689 1
                $this->semanticalError(
690 1
                    "The partial field selection of class " . $class->name . " must contain the identifier.",
691 10
                    $deferredItem['token']
692
                );
693
            }
694
        }
695 9
    }
696
697
    /**
698
     * Validates that the given <tt>ResultVariable</tt> is semantically correct.
699
     * It must exist in query components list.
700
     *
701
     * @return void
702
     */
703 30 View Code Duplication
    private function processDeferredResultVariables()
704
    {
705 30
        foreach ($this->deferredResultVariables as $deferredItem) {
706 30
            $resultVariable = $deferredItem['expression'];
707
708
            // Check if ResultVariable exists in queryComponents
709 30
            if ( ! isset($this->queryComponents[$resultVariable])) {
710
                $this->semanticalError(
711
                    "'$resultVariable' is not defined.", $deferredItem['token']
712
                );
713
            }
714
715 30
            $qComp = $this->queryComponents[$resultVariable];
716
717
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
718 30
            if ( ! isset($qComp['resultVariable'])) {
719
                $this->semanticalError(
720
                    "'$resultVariable' does not point to a ResultVariable.", $deferredItem['token']
721
                );
722
            }
723
724
            // Validate if identification variable nesting level is lower or equal than the current one
725 30
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
726
                $this->semanticalError(
727 30
                    "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
728
                );
729
            }
730
        }
731 30
    }
732
733
    /**
734
     * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
735
     *
736
     * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
737
     * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
738
     * StateFieldPathExpression              ::= IdentificationVariable "." StateField
739
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
740
     * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
741
     *
742
     * @return void
743
     */
744 594
    private function processDeferredPathExpressions()
745
    {
746 594
        foreach ($this->deferredPathExpressions as $deferredItem) {
747 594
            $pathExpression = $deferredItem['expression'];
748
749 594
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
750 594
            $class = $qComp['metadata'];
751
752 594
            if (($field = $pathExpression->field) === null) {
753 39
                $field = $pathExpression->field = $class->identifier[0];
754
            }
755
756
            // Check if field or association exists
757 594
            if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
758 1
                $this->semanticalError(
759 1
                    'Class ' . $class->name . ' has no field or association named ' . $field,
760 1
                    $deferredItem['token']
761
                );
762
            }
763
764 593
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
765
766 593
            if (isset($class->associationMappings[$field])) {
767 87
                $assoc = $class->associationMappings[$field];
768
769 87
                $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
770 66
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
771 87
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
772
            }
773
774
            // Validate if PathExpression is one of the expected types
775 593
            $expectedType = $pathExpression->expectedType;
776
777 593
            if ( ! ($expectedType & $fieldType)) {
778
                // We need to recognize which was expected type(s)
779 2
                $expectedStringTypes = [];
780
781
                // Validate state field type
782 2
                if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
783 1
                    $expectedStringTypes[] = 'StateFieldPathExpression';
784
                }
785
786
                // Validate single valued association (*-to-one)
787 2
                if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
788 2
                    $expectedStringTypes[] = 'SingleValuedAssociationField';
789
                }
790
791
                // Validate single valued association (*-to-many)
792 2
                if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
793
                    $expectedStringTypes[] = 'CollectionValuedAssociationField';
794
                }
795
796
                // Build the error message
797 2
                $semanticalError  = 'Invalid PathExpression. ';
798 2
                $semanticalError .= (count($expectedStringTypes) == 1)
799 1
                    ? 'Must be a ' . $expectedStringTypes[0] . '.'
800 2
                    : implode(' or ', $expectedStringTypes) . ' expected.';
801
802 2
                $this->semanticalError($semanticalError, $deferredItem['token']);
803
            }
804
805
            // We need to force the type in PathExpression
806 591
            $pathExpression->type = $fieldType;
807
        }
808 591
    }
809
810
    /**
811
     * @return void
812
     */
813 786
    private function processRootEntityAliasSelected()
814
    {
815 786
        if ( ! count($this->identVariableExpressions)) {
816 225
            return;
817
        }
818
819 572
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
820 572
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
821 572
                return;
822
            }
823
        }
824
825 1
        $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
826
    }
827
828
    /**
829
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
830
     *
831
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
832
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
833
     *         \Doctrine\ORM\Query\AST\DeleteStatement
834
     */
835 839
    public function QueryLanguage()
836
    {
837 839
        $this->lexer->moveNext();
838
839 839
        switch ($this->lexer->lookahead['type']) {
840 839
            case Lexer::T_SELECT:
841 774
                $statement = $this->SelectStatement();
842 736
                break;
843
844 72
            case Lexer::T_UPDATE:
845 32
                $statement = $this->UpdateStatement();
846 32
                break;
847
848 42
            case Lexer::T_DELETE:
849 41
                $statement = $this->DeleteStatement();
850 40
                break;
851
852
            default:
853 2
                $this->syntaxError('SELECT, UPDATE or DELETE');
854
                break;
855
        }
856
857
        // Check for end of string
858 800
        if ($this->lexer->lookahead !== null) {
859 3
            $this->syntaxError('end of string');
860
        }
861
862 797
        return $statement;
863
    }
864
865
    /**
866
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
867
     *
868
     * @return \Doctrine\ORM\Query\AST\SelectStatement
869
     */
870 774
    public function SelectStatement()
871
    {
872 774
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
873
874 740
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
875 737
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
876 736
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
877 736
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
878
879 736
        return $selectStatement;
880
    }
881
882
    /**
883
     * UpdateStatement ::= UpdateClause [WhereClause]
884
     *
885
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
886
     */
887 32 View Code Duplication
    public function UpdateStatement()
888
    {
889 32
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
890
891 32
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
892
893 32
        return $updateStatement;
894
    }
895
896
    /**
897
     * DeleteStatement ::= DeleteClause [WhereClause]
898
     *
899
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
900
     */
901 41 View Code Duplication
    public function DeleteStatement()
902
    {
903 41
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
904
905 40
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
906
907 40
        return $deleteStatement;
908
    }
909
910
    /**
911
     * IdentificationVariable ::= identifier
912
     *
913
     * @return string
914
     */
915 816 View Code Duplication
    public function IdentificationVariable()
916
    {
917 816
        $this->match(Lexer::T_IDENTIFIER);
918
919 816
        $identVariable = $this->lexer->token['value'];
920
921 816
        $this->deferredIdentificationVariables[] = [
922 816
            'expression'   => $identVariable,
923 816
            'nestingLevel' => $this->nestingLevel,
924 816
            'token'        => $this->lexer->token,
925
        ];
926
927 816
        return $identVariable;
928
    }
929
930
    /**
931
     * AliasIdentificationVariable = identifier
932
     *
933
     * @return string
934
     */
935 808 View Code Duplication
    public function AliasIdentificationVariable()
936
    {
937 808
        $this->match(Lexer::T_IDENTIFIER);
938
939 808
        $aliasIdentVariable = $this->lexer->token['value'];
940 808
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
941
942 808
        if ($exists) {
943 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
944
        }
945
946 808
        return $aliasIdentVariable;
947
    }
948
949
    /**
950
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
951
     *
952
     * @return string
953
     */
954 829
    public function AbstractSchemaName()
955
    {
956 829
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
957 811
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
958
959 811
            $schemaName = $this->lexer->token['value'];
960 29
        } else if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
961 19
            $this->match(Lexer::T_IDENTIFIER);
962
963 19
            $schemaName = $this->lexer->token['value'];
964
        } else {
965 11
            $this->match(Lexer::T_ALIASED_NAME);
966
967 10
            list($namespaceAlias, $simpleClassName) = explode(':', $this->lexer->token['value']);
968
969 10
            $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
970
        }
971
972 828
        return $schemaName;
973
    }
974
975
    /**
976
     * Validates an AbstractSchemaName, making sure the class exists.
977
     *
978
     * @param string $schemaName The name to validate.
979
     *
980
     * @throws QueryException if the name does not exist.
981
     */
982 823
    private function validateAbstractSchemaName($schemaName)
983
    {
984 823
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
985 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
986
        }
987 808
    }
988
989
    /**
990
     * AliasResultVariable ::= identifier
991
     *
992
     * @return string
993
     */
994 119 View Code Duplication
    public function AliasResultVariable()
995
    {
996 119
        $this->match(Lexer::T_IDENTIFIER);
997
998 115
        $resultVariable = $this->lexer->token['value'];
999 115
        $exists = isset($this->queryComponents[$resultVariable]);
1000
1001 115
        if ($exists) {
1002 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1003
        }
1004
1005 115
        return $resultVariable;
1006
    }
1007
1008
    /**
1009
     * ResultVariable ::= identifier
1010
     *
1011
     * @return string
1012
     */
1013 30 View Code Duplication
    public function ResultVariable()
1014
    {
1015 30
        $this->match(Lexer::T_IDENTIFIER);
1016
1017 30
        $resultVariable = $this->lexer->token['value'];
1018
1019
        // Defer ResultVariable validation
1020 30
        $this->deferredResultVariables[] = [
1021 30
            'expression'   => $resultVariable,
1022 30
            'nestingLevel' => $this->nestingLevel,
1023 30
            'token'        => $this->lexer->token,
1024
        ];
1025
1026 30
        return $resultVariable;
1027
    }
1028
1029
    /**
1030
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1031
     *
1032
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1033
     */
1034 257
    public function JoinAssociationPathExpression()
1035
    {
1036 257
        $identVariable = $this->IdentificationVariable();
1037
1038 257
        if ( ! isset($this->queryComponents[$identVariable])) {
1039
            $this->semanticalError(
1040
                'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.'
1041
            );
1042
        }
1043
1044 257
        $this->match(Lexer::T_DOT);
1045 257
        $this->match(Lexer::T_IDENTIFIER);
1046
1047 257
        $field = $this->lexer->token['value'];
1048
1049
        // Validate association field
1050 257
        $qComp = $this->queryComponents[$identVariable];
1051 257
        $class = $qComp['metadata'];
1052
1053 257
        if ( ! $class->hasAssociation($field)) {
1054
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
1055
        }
1056
1057 257
        return new AST\JoinAssociationPathExpression($identVariable, $field);
1058
    }
1059
1060
    /**
1061
     * Parses an arbitrary path expression and defers semantical validation
1062
     * based on expected types.
1063
     *
1064
     * PathExpression ::= IdentificationVariable {"." identifier}*
1065
     *
1066
     * @param integer $expectedTypes
1067
     *
1068
     * @return \Doctrine\ORM\Query\AST\PathExpression
1069
     */
1070 604
    public function PathExpression($expectedTypes)
1071
    {
1072 604
        $identVariable = $this->IdentificationVariable();
1073 604
        $field = null;
1074
1075 604
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1076 597
            $this->match(Lexer::T_DOT);
1077 597
            $this->match(Lexer::T_IDENTIFIER);
1078
1079 597
            $field = $this->lexer->token['value'];
1080
1081 597 View Code Duplication
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1082 2
                $this->match(Lexer::T_DOT);
1083 2
                $this->match(Lexer::T_IDENTIFIER);
1084 2
                $field .= '.'.$this->lexer->token['value'];
1085
            }
1086
        }
1087
1088
        // Creating AST node
1089 604
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1090
1091
        // Defer PathExpression validation if requested to be deferred
1092 604
        $this->deferredPathExpressions[] = [
1093 604
            'expression'   => $pathExpr,
1094 604
            'nestingLevel' => $this->nestingLevel,
1095 604
            'token'        => $this->lexer->token,
1096
        ];
1097
1098 604
        return $pathExpr;
1099
    }
1100
1101
    /**
1102
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
1103
     *
1104
     * @return \Doctrine\ORM\Query\AST\PathExpression
1105
     */
1106
    public function AssociationPathExpression()
1107
    {
1108
        return $this->PathExpression(
1109
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
1110
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
1111
        );
1112
    }
1113
1114
    /**
1115
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1116
     *
1117
     * @return \Doctrine\ORM\Query\AST\PathExpression
1118
     */
1119 512
    public function SingleValuedPathExpression()
1120
    {
1121 512
        return $this->PathExpression(
1122 512
            AST\PathExpression::TYPE_STATE_FIELD |
1123 512
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1124
        );
1125
    }
1126
1127
    /**
1128
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1129
     *
1130
     * @return \Doctrine\ORM\Query\AST\PathExpression
1131
     */
1132 204
    public function StateFieldPathExpression()
1133
    {
1134 204
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1135
    }
1136
1137
    /**
1138
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1139
     *
1140
     * @return \Doctrine\ORM\Query\AST\PathExpression
1141
     */
1142 9
    public function SingleValuedAssociationPathExpression()
1143
    {
1144 9
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1145
    }
1146
1147
    /**
1148
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1149
     *
1150
     * @return \Doctrine\ORM\Query\AST\PathExpression
1151
     */
1152 21
    public function CollectionValuedPathExpression()
1153
    {
1154 21
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1155
    }
1156
1157
    /**
1158
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1159
     *
1160
     * @return \Doctrine\ORM\Query\AST\SelectClause
1161
     */
1162 774
    public function SelectClause()
1163
    {
1164 774
        $isDistinct = false;
1165 774
        $this->match(Lexer::T_SELECT);
1166
1167
        // Check for DISTINCT
1168 774
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1169 6
            $this->match(Lexer::T_DISTINCT);
1170
1171 6
            $isDistinct = true;
1172
        }
1173
1174
        // Process SelectExpressions (1..N)
1175 774
        $selectExpressions = [];
1176 774
        $selectExpressions[] = $this->SelectExpression();
1177
1178 766
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1179 285
            $this->match(Lexer::T_COMMA);
1180
1181 285
            $selectExpressions[] = $this->SelectExpression();
1182
        }
1183
1184 765
        return new AST\SelectClause($selectExpressions, $isDistinct);
1185
    }
1186
1187
    /**
1188
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1189
     *
1190
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1191
     */
1192 48 View Code Duplication
    public function SimpleSelectClause()
1193
    {
1194 48
        $isDistinct = false;
1195 48
        $this->match(Lexer::T_SELECT);
1196
1197 48
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1198
            $this->match(Lexer::T_DISTINCT);
1199
1200
            $isDistinct = true;
1201
        }
1202
1203 48
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1204
    }
1205
1206
    /**
1207
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1208
     *
1209
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1210
     */
1211 32
    public function UpdateClause()
1212
    {
1213 32
        $this->match(Lexer::T_UPDATE);
1214
1215 32
        $token = $this->lexer->lookahead;
1216 32
        $abstractSchemaName = $this->AbstractSchemaName();
1217
1218 32
        $this->validateAbstractSchemaName($abstractSchemaName);
1219
1220 32
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1221 2
            $this->match(Lexer::T_AS);
1222
        }
1223
1224 32
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1225
1226 32
        $class = $this->em->getClassMetadata($abstractSchemaName);
1227
1228
        // Building queryComponent
1229
        $queryComponent = [
1230 32
            'metadata'     => $class,
1231
            'parent'       => null,
1232
            'relation'     => null,
1233
            'map'          => null,
1234 32
            'nestingLevel' => $this->nestingLevel,
1235 32
            'token'        => $token,
1236
        ];
1237
1238 32
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1239
1240 32
        $this->match(Lexer::T_SET);
1241
1242 32
        $updateItems = [];
1243 32
        $updateItems[] = $this->UpdateItem();
1244
1245 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1246 5
            $this->match(Lexer::T_COMMA);
1247
1248 5
            $updateItems[] = $this->UpdateItem();
1249
        }
1250
1251 32
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1252 32
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1253
1254 32
        return $updateClause;
1255
    }
1256
1257
    /**
1258
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1259
     *
1260
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1261
     */
1262 41
    public function DeleteClause()
1263
    {
1264 41
        $this->match(Lexer::T_DELETE);
1265
1266 41
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1267 8
            $this->match(Lexer::T_FROM);
1268
        }
1269
1270 41
        $token = $this->lexer->lookahead;
1271 41
        $abstractSchemaName = $this->AbstractSchemaName();
1272
1273 41
        $this->validateAbstractSchemaName($abstractSchemaName);
1274
1275 41
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1276
1277 41
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1278 1
            $this->match(Lexer::T_AS);
1279
        }
1280
1281 41
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1282
1283 40
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1284 40
        $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1285
1286
        // Building queryComponent
1287
        $queryComponent = [
1288 40
            'metadata'     => $class,
1289
            'parent'       => null,
1290
            'relation'     => null,
1291
            'map'          => null,
1292 40
            'nestingLevel' => $this->nestingLevel,
1293 40
            'token'        => $token,
1294
        ];
1295
1296 40
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1297
1298 40
        return $deleteClause;
1299
    }
1300
1301
    /**
1302
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1303
     *
1304
     * @return \Doctrine\ORM\Query\AST\FromClause
1305
     */
1306 765
    public function FromClause()
1307
    {
1308 765
        $this->match(Lexer::T_FROM);
1309
1310 760
        $identificationVariableDeclarations = [];
1311 760
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1312
1313 740
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1314 6
            $this->match(Lexer::T_COMMA);
1315
1316 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1317
        }
1318
1319 740
        return new AST\FromClause($identificationVariableDeclarations);
1320
    }
1321
1322
    /**
1323
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1324
     *
1325
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1326
     */
1327 48
    public function SubselectFromClause()
1328
    {
1329 48
        $this->match(Lexer::T_FROM);
1330
1331 48
        $identificationVariables = [];
1332 48
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1333
1334 47
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1335
            $this->match(Lexer::T_COMMA);
1336
1337
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1338
        }
1339
1340 47
        return new AST\SubselectFromClause($identificationVariables);
1341
    }
1342
1343
    /**
1344
     * WhereClause ::= "WHERE" ConditionalExpression
1345
     *
1346
     * @return \Doctrine\ORM\Query\AST\WhereClause
1347
     */
1348 340
    public function WhereClause()
1349
    {
1350 340
        $this->match(Lexer::T_WHERE);
1351
1352 340
        return new AST\WhereClause($this->ConditionalExpression());
1353
    }
1354
1355
    /**
1356
     * HavingClause ::= "HAVING" ConditionalExpression
1357
     *
1358
     * @return \Doctrine\ORM\Query\AST\HavingClause
1359
     */
1360 21
    public function HavingClause()
1361
    {
1362 21
        $this->match(Lexer::T_HAVING);
1363
1364 21
        return new AST\HavingClause($this->ConditionalExpression());
1365
    }
1366
1367
    /**
1368
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1369
     *
1370
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1371
     */
1372 32
    public function GroupByClause()
1373
    {
1374 32
        $this->match(Lexer::T_GROUP);
1375 32
        $this->match(Lexer::T_BY);
1376
1377 32
        $groupByItems = [$this->GroupByItem()];
1378
1379 31
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1380 8
            $this->match(Lexer::T_COMMA);
1381
1382 8
            $groupByItems[] = $this->GroupByItem();
1383
        }
1384
1385 31
        return new AST\GroupByClause($groupByItems);
1386
    }
1387
1388
    /**
1389
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1390
     *
1391
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1392
     */
1393 179
    public function OrderByClause()
1394
    {
1395 179
        $this->match(Lexer::T_ORDER);
1396 179
        $this->match(Lexer::T_BY);
1397
1398 179
        $orderByItems = [];
1399 179
        $orderByItems[] = $this->OrderByItem();
1400
1401 179
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1402 14
            $this->match(Lexer::T_COMMA);
1403
1404 14
            $orderByItems[] = $this->OrderByItem();
1405
        }
1406
1407 179
        return new AST\OrderByClause($orderByItems);
1408
    }
1409
1410
    /**
1411
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1412
     *
1413
     * @return \Doctrine\ORM\Query\AST\Subselect
1414
     */
1415 48
    public function Subselect()
1416
    {
1417
        // Increase query nesting level
1418 48
        $this->nestingLevel++;
1419
1420 48
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1421
1422 47
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1423 47
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1424 47
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1425 47
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1426
1427
        // Decrease query nesting level
1428 47
        $this->nestingLevel--;
1429
1430 47
        return $subselect;
1431
    }
1432
1433
    /**
1434
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1435
     *
1436
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1437
     */
1438 32
    public function UpdateItem()
1439
    {
1440 32
        $pathExpr = $this->SingleValuedPathExpression();
1441
1442 32
        $this->match(Lexer::T_EQUALS);
1443
1444 32
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1445
1446 32
        return $updateItem;
1447
    }
1448
1449
    /**
1450
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1451
     *
1452
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1453
     */
1454 32
    public function GroupByItem()
1455
    {
1456
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1457 32
        $glimpse = $this->lexer->glimpse();
1458
1459 32
        if ($glimpse['type'] === Lexer::T_DOT) {
1460 13
            return $this->SingleValuedPathExpression();
1461
        }
1462
1463
        // Still need to decide between IdentificationVariable or ResultVariable
1464 19
        $lookaheadValue = $this->lexer->lookahead['value'];
1465
1466 19
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1467 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1468
        }
1469
1470 18
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1471 16
            ? $this->IdentificationVariable()
1472 18
            : $this->ResultVariable();
1473
    }
1474
1475
    /**
1476
     * OrderByItem ::= (
1477
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1478
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1479
     * ) ["ASC" | "DESC"]
1480
     *
1481
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1482
     */
1483 179
    public function OrderByItem()
1484
    {
1485 179
        $this->lexer->peek(); // lookahead => '.'
1486 179
        $this->lexer->peek(); // lookahead => token after '.'
1487
1488 179
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1489
1490 179
        $this->lexer->resetPeek();
1491
1492 179
        $glimpse = $this->lexer->glimpse();
1493
1494
        switch (true) {
1495 179
            case ($this->isFunction()):
1496 1
                $expr = $this->FunctionDeclaration();
1497 1
                break;
1498
1499 178
            case ($this->isMathOperator($peek)):
1500 25
                $expr = $this->SimpleArithmeticExpression();
1501 25
                break;
1502
1503 154
            case ($glimpse['type'] === Lexer::T_DOT):
1504 141
                $expr = $this->SingleValuedPathExpression();
1505 141
                break;
1506
1507 17
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1508 2
                $expr = $this->ScalarExpression();
1509 2
                break;
1510
1511
            default:
1512 15
                $expr = $this->ResultVariable();
1513 15
                break;
1514
        }
1515
1516 179
        $type = 'ASC';
1517 179
        $item = new AST\OrderByItem($expr);
1518
1519
        switch (true) {
1520 179
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1521 93
                $this->match(Lexer::T_DESC);
1522 93
                $type = 'DESC';
1523 93
                break;
1524
1525 152
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1526 96
                $this->match(Lexer::T_ASC);
1527 96
                break;
1528
1529
            default:
1530
                // Do nothing
1531
        }
1532
1533 179
        $item->type = $type;
1534
1535 179
        return $item;
1536
    }
1537
1538
    /**
1539
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1540
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1541
     *
1542
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1543
     * grammar that needs to be supported:
1544
     *
1545
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1546
     *
1547
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1548
     *
1549
     * @return AST\ArithmeticExpression
1550
     */
1551 32
    public function NewValue()
1552
    {
1553 32
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1554 1
            $this->match(Lexer::T_NULL);
1555
1556 1
            return null;
1557
        }
1558
1559 31 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1560 19
            $this->match(Lexer::T_INPUT_PARAMETER);
1561
1562 19
            return new AST\InputParameter($this->lexer->token['value']);
1563
        }
1564
1565 12
        return $this->ArithmeticExpression();
1566
    }
1567
1568
    /**
1569
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1570
     *
1571
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1572
     */
1573 762
    public function IdentificationVariableDeclaration()
1574
    {
1575 762
        $joins                    = [];
1576 762
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1577 745
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1578 8
            ? $this->IndexBy()
1579 745
            : null;
1580
1581 745
        $rangeVariableDeclaration->isRoot = true;
1582
1583
        while (
1584 745
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1585 745
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1586 745
            $this->lexer->isNextToken(Lexer::T_JOIN)
1587
        ) {
1588 280
            $joins[] = $this->Join();
1589
        }
1590
1591 742
        return new AST\IdentificationVariableDeclaration(
1592 742
            $rangeVariableDeclaration, $indexBy, $joins
1593
        );
1594
    }
1595
1596
    /**
1597
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1598
     *
1599
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1600
     * Desired EBNF support:
1601
     *
1602
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1603
     *
1604
     * It demands that entire SQL generation to become programmatical. This is
1605
     * needed because association based subselect requires "WHERE" conditional
1606
     * expressions to be injected, but there is no scope to do that. Only scope
1607
     * accessible is "FROM", prohibiting an easy implementation without larger
1608
     * changes.}
1609
     *
1610
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1611
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1612
     */
1613 48
    public function SubselectIdentificationVariableDeclaration()
1614
    {
1615
        /*
1616
        NOT YET IMPLEMENTED!
1617
1618
        $glimpse = $this->lexer->glimpse();
1619
1620
        if ($glimpse['type'] == Lexer::T_DOT) {
1621
            $associationPathExpression = $this->AssociationPathExpression();
1622
1623
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1624
                $this->match(Lexer::T_AS);
1625
            }
1626
1627
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1628
            $identificationVariable      = $associationPathExpression->identificationVariable;
1629
            $field                       = $associationPathExpression->associationField;
1630
1631
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1632
            $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1633
1634
            // Building queryComponent
1635
            $joinQueryComponent = array(
1636
                'metadata'     => $targetClass,
1637
                'parent'       => $identificationVariable,
1638
                'relation'     => $class->getAssociationMapping($field),
1639
                'map'          => null,
1640
                'nestingLevel' => $this->nestingLevel,
1641
                'token'        => $this->lexer->lookahead
1642
            );
1643
1644
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1645
1646
            return new AST\SubselectIdentificationVariableDeclaration(
1647
                $associationPathExpression, $aliasIdentificationVariable
1648
            );
1649
        }
1650
        */
1651
1652 48
        return $this->IdentificationVariableDeclaration();
1653
    }
1654
1655
    /**
1656
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1657
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1658
     *          ["WITH" ConditionalExpression]
1659
     *
1660
     * @return \Doctrine\ORM\Query\AST\Join
1661
     */
1662 280
    public function Join()
1663
    {
1664
        // Check Join type
1665 280
        $joinType = AST\Join::JOIN_TYPE_INNER;
1666
1667
        switch (true) {
1668 280
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1669 68
                $this->match(Lexer::T_LEFT);
1670
1671 68
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1672
1673
                // Possible LEFT OUTER join
1674 68
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1675
                    $this->match(Lexer::T_OUTER);
1676
1677
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1678
                }
1679 68
                break;
1680
1681 216
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1682 21
                $this->match(Lexer::T_INNER);
1683 21
                break;
1684
1685
            default:
1686
                // Do nothing
1687
        }
1688
1689 280
        $this->match(Lexer::T_JOIN);
1690
1691 280
        $next            = $this->lexer->glimpse();
1692 280
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1693 277
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1694 277
        $join            = new AST\Join($joinType, $joinDeclaration);
1695
1696
        // Describe non-root join declaration
1697 277
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1698 22
            $joinDeclaration->isRoot = false;
1699
        }
1700
1701
        // Check for ad-hoc Join conditions
1702 277
        if ($adhocConditions) {
1703 24
            $this->match(Lexer::T_WITH);
1704
1705 24
            $join->conditionalExpression = $this->ConditionalExpression();
1706
        }
1707
1708 277
        return $join;
1709
    }
1710
1711
    /**
1712
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1713
     *
1714
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1715
     *
1716
     * @throws QueryException
1717
     */
1718 762
    public function RangeVariableDeclaration()
1719
    {
1720 762
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
1721 2
            $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1722
        }
1723
1724 761
        $abstractSchemaName = $this->AbstractSchemaName();
1725
1726 760
        $this->validateAbstractSchemaName($abstractSchemaName);
1727
1728 745
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1729 6
            $this->match(Lexer::T_AS);
1730
        }
1731
1732 745
        $token = $this->lexer->lookahead;
1733 745
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1734 745
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1735
1736
        // Building queryComponent
1737
        $queryComponent = [
1738 745
            'metadata'     => $classMetadata,
1739
            'parent'       => null,
1740
            'relation'     => null,
1741
            'map'          => null,
1742 745
            'nestingLevel' => $this->nestingLevel,
1743 745
            'token'        => $token
1744
        ];
1745
1746 745
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1747
1748 745
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1749
    }
1750
1751
    /**
1752
     * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1753
     *
1754
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1755
     */
1756 257
    public function JoinAssociationDeclaration()
1757
    {
1758 257
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1759
1760 257
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1761 5
            $this->match(Lexer::T_AS);
1762
        }
1763
1764 257
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1765 255
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1766
1767 255
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1768 255
        $field                  = $joinAssociationPathExpression->associationField;
1769
1770 255
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1771 255
        $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1772
1773
        // Building queryComponent
1774
        $joinQueryComponent = [
1775 255
            'metadata'     => $targetClass,
1776 255
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1777 255
            'relation'     => $class->getAssociationMapping($field),
1778
            'map'          => null,
1779 255
            'nestingLevel' => $this->nestingLevel,
1780 255
            'token'        => $this->lexer->lookahead
1781
        ];
1782
1783 255
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1784
1785 255
        return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
1786
    }
1787
1788
    /**
1789
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1790
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1791
     *
1792
     * @return \Doctrine\ORM\Query\AST\PartialObjectExpression
1793
     */
1794 11
    public function PartialObjectExpression()
1795
    {
1796 11
        $this->match(Lexer::T_PARTIAL);
1797
1798 11
        $partialFieldSet = [];
1799
1800 11
        $identificationVariable = $this->IdentificationVariable();
1801
1802 11
        $this->match(Lexer::T_DOT);
1803 11
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1804 11
        $this->match(Lexer::T_IDENTIFIER);
1805
1806 11
        $field = $this->lexer->token['value'];
1807
1808
        // First field in partial expression might be embeddable property
1809 11 View Code Duplication
        while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1810 1
            $this->match(Lexer::T_DOT);
1811 1
            $this->match(Lexer::T_IDENTIFIER);
1812 1
            $field .= '.'.$this->lexer->token['value'];
1813
        }
1814
1815 11
        $partialFieldSet[] = $field;
1816
1817 11
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1818 9
            $this->match(Lexer::T_COMMA);
1819 9
            $this->match(Lexer::T_IDENTIFIER);
1820
1821 9
            $field = $this->lexer->token['value'];
1822
1823 9 View Code Duplication
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1824 2
                $this->match(Lexer::T_DOT);
1825 2
                $this->match(Lexer::T_IDENTIFIER);
1826 2
                $field .= '.'.$this->lexer->token['value'];
1827
            }
1828
1829 9
            $partialFieldSet[] = $field;
1830
        }
1831
1832 11
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1833
1834 11
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1835
1836
        // Defer PartialObjectExpression validation
1837 11
        $this->deferredPartialObjectExpressions[] = [
1838 11
            'expression'   => $partialObjectExpression,
1839 11
            'nestingLevel' => $this->nestingLevel,
1840 11
            'token'        => $this->lexer->token,
1841
        ];
1842
1843 11
        return $partialObjectExpression;
1844
    }
1845
1846
    /**
1847
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1848
     *
1849
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1850
     */
1851 28
    public function NewObjectExpression()
1852
    {
1853 28
        $this->match(Lexer::T_NEW);
1854
1855 28
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1856 28
        $token = $this->lexer->token;
1857
1858 28
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1859
1860 28
        $args[] = $this->NewObjectArg();
1861
1862 28
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1863 24
            $this->match(Lexer::T_COMMA);
1864
1865 24
            $args[] = $this->NewObjectArg();
1866
        }
1867
1868 28
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1869
1870 28
        $expression = new AST\NewObjectExpression($className, $args);
1871
1872
        // Defer NewObjectExpression validation
1873 28
        $this->deferredNewObjectExpressions[] = [
1874 28
            'token'        => $token,
1875 28
            'expression'   => $expression,
1876 28
            'nestingLevel' => $this->nestingLevel,
1877
        ];
1878
1879 28
        return $expression;
1880
    }
1881
1882
    /**
1883
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1884
     *
1885
     * @return mixed
1886
     */
1887 28
    public function NewObjectArg()
1888
    {
1889 28
        $token = $this->lexer->lookahead;
1890 28
        $peek  = $this->lexer->glimpse();
1891
1892 28
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1893 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1894 2
            $expression = $this->Subselect();
1895 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1896
1897 2
            return $expression;
1898
        }
1899
1900 28
        return $this->ScalarExpression();
1901
    }
1902
1903
    /**
1904
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1905
     *
1906
     * @return \Doctrine\ORM\Query\AST\IndexBy
1907
     */
1908 12
    public function IndexBy()
1909
    {
1910 12
        $this->match(Lexer::T_INDEX);
1911 12
        $this->match(Lexer::T_BY);
1912 12
        $pathExpr = $this->StateFieldPathExpression();
1913
1914
        // Add the INDEX BY info to the query component
1915 12
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1916
1917 12
        return new AST\IndexBy($pathExpr);
1918
    }
1919
1920
    /**
1921
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1922
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1923
     *                      InstanceOfExpression
1924
     *
1925
     * @return mixed One of the possible expressions or subexpressions.
1926
     */
1927 161
    public function ScalarExpression()
1928
    {
1929 161
        $lookahead = $this->lexer->lookahead['type'];
1930 161
        $peek      = $this->lexer->glimpse();
1931
1932
        switch (true) {
1933 161
            case ($lookahead === Lexer::T_INTEGER):
1934 158
            case ($lookahead === Lexer::T_FLOAT):
1935
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1936 158
            case ($lookahead === Lexer::T_MINUS):
1937 158
            case ($lookahead === Lexer::T_PLUS):
1938 17
                return $this->SimpleArithmeticExpression();
1939
1940 158
            case ($lookahead === Lexer::T_STRING):
1941 13
                return $this->StringPrimary();
1942
1943 156
            case ($lookahead === Lexer::T_TRUE):
1944 156 View Code Duplication
            case ($lookahead === Lexer::T_FALSE):
1945 3
                $this->match($lookahead);
1946
1947 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1948
1949 156
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
1950
                switch (true) {
1951 1
                    case $this->isMathOperator($peek):
0 ignored issues
show
It seems like $peek defined by $this->lexer->glimpse() on line 1930 can also be of type null; however, Doctrine\ORM\Query\Parser::isMathOperator() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1952
                        // :param + u.value
1953 1
                        return $this->SimpleArithmeticExpression();
1954
                    default:
1955
                        return $this->InputParameter();
1956
                }
1957
1958 156
            case ($lookahead === Lexer::T_CASE):
1959 152
            case ($lookahead === Lexer::T_COALESCE):
1960 152
            case ($lookahead === Lexer::T_NULLIF):
1961
                // Since NULLIF and COALESCE can be identified as a function,
1962
                // we need to check these before checking for FunctionDeclaration
1963 8
                return $this->CaseExpression();
1964
1965 152
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1966 4
                return $this->SimpleArithmeticExpression();
1967
1968
            // this check must be done before checking for a filed path expression
1969 149
            case ($this->isFunction()):
1970 26
                $this->lexer->peek(); // "("
1971
1972
                switch (true) {
1973 26
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1974
                        // SUM(u.id) + COUNT(u.id)
1975 7
                        return $this->SimpleArithmeticExpression();
1976
1977
                    default:
1978
                        // IDENTITY(u)
1979 21
                        return $this->FunctionDeclaration();
1980
                }
1981
1982
                break;
1983
            // it is no function, so it must be a field path
1984 131
            case ($lookahead === Lexer::T_IDENTIFIER):
1985 131
                $this->lexer->peek(); // lookahead => '.'
1986 131
                $this->lexer->peek(); // lookahead => token after '.'
1987 131
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1988 131
                $this->lexer->resetPeek();
1989
1990 131
                if ($this->isMathOperator($peek)) {
1991 7
                    return $this->SimpleArithmeticExpression();
1992
                }
1993
1994 126
                return $this->StateFieldPathExpression();
1995
1996
            default:
1997
                $this->syntaxError();
1998
        }
1999
    }
2000
2001
    /**
2002
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
2003
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2004
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2005
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2006
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2007
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2008
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2009
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2010
     *
2011
     * @return mixed One of the possible expressions or subexpressions.
2012
     */
2013 19
    public function CaseExpression()
2014
    {
2015 19
        $lookahead = $this->lexer->lookahead['type'];
2016
2017
        switch ($lookahead) {
2018 19
            case Lexer::T_NULLIF:
2019 5
                return $this->NullIfExpression();
2020
2021 16
            case Lexer::T_COALESCE:
2022 2
                return $this->CoalesceExpression();
2023
2024 14
            case Lexer::T_CASE:
2025 14
                $this->lexer->resetPeek();
2026 14
                $peek = $this->lexer->peek();
2027
2028 14
                if ($peek['type'] === Lexer::T_WHEN) {
2029 9
                    return $this->GeneralCaseExpression();
2030
                }
2031
2032 5
                return $this->SimpleCaseExpression();
2033
2034
            default:
2035
                // Do nothing
2036
                break;
2037
        }
2038
2039
        $this->syntaxError();
2040
    }
2041
2042
    /**
2043
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2044
     *
2045
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2046
     */
2047 3
    public function CoalesceExpression()
2048
    {
2049 3
        $this->match(Lexer::T_COALESCE);
2050 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2051
2052
        // Process ScalarExpressions (1..N)
2053 3
        $scalarExpressions = [];
2054 3
        $scalarExpressions[] = $this->ScalarExpression();
2055
2056 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2057 3
            $this->match(Lexer::T_COMMA);
2058
2059 3
            $scalarExpressions[] = $this->ScalarExpression();
2060
        }
2061
2062 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2063
2064 3
        return new AST\CoalesceExpression($scalarExpressions);
2065
    }
2066
2067
    /**
2068
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2069
     *
2070
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2071
     */
2072 5
    public function NullIfExpression()
2073
    {
2074 5
        $this->match(Lexer::T_NULLIF);
2075 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2076
2077 5
        $firstExpression = $this->ScalarExpression();
2078 5
        $this->match(Lexer::T_COMMA);
2079 5
        $secondExpression = $this->ScalarExpression();
2080
2081 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2082
2083 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2084
    }
2085
2086
    /**
2087
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2088
     *
2089
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2090
     */
2091 9 View Code Duplication
    public function GeneralCaseExpression()
2092
    {
2093 9
        $this->match(Lexer::T_CASE);
2094
2095
        // Process WhenClause (1..N)
2096 9
        $whenClauses = [];
2097
2098
        do {
2099 9
            $whenClauses[] = $this->WhenClause();
2100 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2101
2102 9
        $this->match(Lexer::T_ELSE);
2103 9
        $scalarExpression = $this->ScalarExpression();
2104 9
        $this->match(Lexer::T_END);
2105
2106 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2107
    }
2108
2109
    /**
2110
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2111
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2112
     *
2113
     * @return AST\SimpleCaseExpression
2114
     */
2115 5 View Code Duplication
    public function SimpleCaseExpression()
2116
    {
2117 5
        $this->match(Lexer::T_CASE);
2118 5
        $caseOperand = $this->StateFieldPathExpression();
2119
2120
        // Process SimpleWhenClause (1..N)
2121 5
        $simpleWhenClauses = [];
2122
2123
        do {
2124 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2125 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2126
2127 5
        $this->match(Lexer::T_ELSE);
2128 5
        $scalarExpression = $this->ScalarExpression();
2129 5
        $this->match(Lexer::T_END);
2130
2131 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2132
    }
2133
2134
    /**
2135
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2136
     *
2137
     * @return \Doctrine\ORM\Query\AST\WhenClause
2138
     */
2139 9
    public function WhenClause()
2140
    {
2141 9
        $this->match(Lexer::T_WHEN);
2142 9
        $conditionalExpression = $this->ConditionalExpression();
2143 9
        $this->match(Lexer::T_THEN);
2144
2145 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2146
    }
2147
2148
    /**
2149
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2150
     *
2151
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2152
     */
2153 5
    public function SimpleWhenClause()
2154
    {
2155 5
        $this->match(Lexer::T_WHEN);
2156 5
        $conditionalExpression = $this->ScalarExpression();
2157 5
        $this->match(Lexer::T_THEN);
2158
2159 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2160
    }
2161
2162
    /**
2163
     * SelectExpression ::= (
2164
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2165
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2166
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2167
     *
2168
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2169
     */
2170 774
    public function SelectExpression()
2171
    {
2172 774
        $expression    = null;
2173 774
        $identVariable = null;
2174 774
        $peek          = $this->lexer->glimpse();
2175 774
        $lookaheadType = $this->lexer->lookahead['type'];
2176
2177
        switch (true) {
2178
            // ScalarExpression (u.name)
2179 774
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2180 103
                $expression = $this->ScalarExpression();
2181 103
                break;
2182
2183
            // IdentificationVariable (u)
2184 714
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2185 598
                $expression = $identVariable = $this->IdentificationVariable();
2186 598
                break;
2187
2188
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
2189 177
            case ($lookaheadType === Lexer::T_CASE):
2190 172
            case ($lookaheadType === Lexer::T_COALESCE):
2191 170
            case ($lookaheadType === Lexer::T_NULLIF):
2192 9
                $expression = $this->CaseExpression();
2193 9
                break;
2194
2195
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2196 168
            case ($this->isFunction()):
2197 88
                $this->lexer->peek(); // "("
2198
2199
                switch (true) {
2200 88
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2201
                        // SUM(u.id) + COUNT(u.id)
2202 2
                        $expression = $this->ScalarExpression();
2203 2
                        break;
2204
2205
                    default:
2206
                        // IDENTITY(u)
2207 86
                        $expression = $this->FunctionDeclaration();
2208 86
                        break;
2209
                }
2210
2211 88
                break;
2212
2213
            // PartialObjectExpression (PARTIAL u.{id, name})
2214 80
            case ($lookaheadType === Lexer::T_PARTIAL):
2215 11
                $expression    = $this->PartialObjectExpression();
2216 11
                $identVariable = $expression->identificationVariable;
2217 11
                break;
2218
2219
            // Subselect
2220 69
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2221 22
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2222 22
                $expression = $this->Subselect();
2223 22
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2224 22
                break;
2225
2226
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2227 47
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2228 43
            case ($lookaheadType === Lexer::T_INTEGER):
2229 41
            case ($lookaheadType === Lexer::T_STRING):
2230 32
            case ($lookaheadType === Lexer::T_FLOAT):
2231
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2232 32
            case ($lookaheadType === Lexer::T_MINUS):
2233 32
            case ($lookaheadType === Lexer::T_PLUS):
2234 16
                $expression = $this->SimpleArithmeticExpression();
2235 16
                break;
2236
2237
            // NewObjectExpression (New ClassName(id, name))
2238 31
            case ($lookaheadType === Lexer::T_NEW):
2239 28
                $expression = $this->NewObjectExpression();
2240 28
                break;
2241
2242
            default:
2243 3
                $this->syntaxError(
2244 3
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2245 3
                    $this->lexer->lookahead
2246
                );
2247
        }
2248
2249
        // [["AS"] ["HIDDEN"] AliasResultVariable]
2250 771
        $mustHaveAliasResultVariable = false;
2251
2252 771
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2253 110
            $this->match(Lexer::T_AS);
2254
2255 110
            $mustHaveAliasResultVariable = true;
2256
        }
2257
2258 771
        $hiddenAliasResultVariable = false;
2259
2260 771
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2261 10
            $this->match(Lexer::T_HIDDEN);
2262
2263 10
            $hiddenAliasResultVariable = true;
2264
        }
2265
2266 771
        $aliasResultVariable = null;
2267
2268 771 View Code Duplication
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2269 119
            $token = $this->lexer->lookahead;
2270 119
            $aliasResultVariable = $this->AliasResultVariable();
2271
2272
            // Include AliasResultVariable in query components.
2273 114
            $this->queryComponents[$aliasResultVariable] = [
2274 114
                'resultVariable' => $expression,
2275 114
                'nestingLevel'   => $this->nestingLevel,
2276 114
                'token'          => $token,
2277
            ];
2278
        }
2279
2280
        // AST
2281
2282 766
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2283
2284 766
        if ($identVariable) {
2285 606
            $this->identVariableExpressions[$identVariable] = $expr;
2286
        }
2287
2288 766
        return $expr;
2289
    }
2290
2291
    /**
2292
     * SimpleSelectExpression ::= (
2293
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2294
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2295
     * ) [["AS"] AliasResultVariable]
2296
     *
2297
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2298
     */
2299 48
    public function SimpleSelectExpression()
2300
    {
2301 48
        $peek = $this->lexer->glimpse();
2302
2303 48
        switch ($this->lexer->lookahead['type']) {
2304 48
            case Lexer::T_IDENTIFIER:
2305
                switch (true) {
2306 19
                    case ($peek['type'] === Lexer::T_DOT):
2307 16
                        $expression = $this->StateFieldPathExpression();
2308
2309 16
                        return new AST\SimpleSelectExpression($expression);
2310
2311 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2312 2
                        $expression = $this->IdentificationVariable();
2313
2314 2
                        return new AST\SimpleSelectExpression($expression);
2315
2316 1
                    case ($this->isFunction()):
2317
                        // SUM(u.id) + COUNT(u.id)
2318 1
                        if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
2319
                            return new AST\SimpleSelectExpression($this->ScalarExpression());
2320
                        }
2321
                        // COUNT(u.id)
2322 1
                        if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
2323
                            return new AST\SimpleSelectExpression($this->AggregateExpression());
2324
                        }
2325
                        // IDENTITY(u)
2326 1
                        return new AST\SimpleSelectExpression($this->FunctionDeclaration());
2327
2328
                    default:
2329
                        // Do nothing
2330
                }
2331
                break;
2332
2333 30
            case Lexer::T_OPEN_PARENTHESIS:
2334 3
                if ($peek['type'] !== Lexer::T_SELECT) {
2335
                    // Shortcut: ScalarExpression => SimpleArithmeticExpression
2336 3
                    $expression = $this->SimpleArithmeticExpression();
2337
2338 3
                    return new AST\SimpleSelectExpression($expression);
2339
                }
2340
2341
                // Subselect
2342
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2343
                $expression = $this->Subselect();
2344
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2345
2346
                return new AST\SimpleSelectExpression($expression);
2347
2348
            default:
2349
                // Do nothing
2350
        }
2351
2352 27
        $this->lexer->peek();
2353
2354 27
        $expression = $this->ScalarExpression();
2355 27
        $expr       = new AST\SimpleSelectExpression($expression);
2356
2357 27
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2358 1
            $this->match(Lexer::T_AS);
2359
        }
2360
2361 27 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2362 2
            $token = $this->lexer->lookahead;
2363 2
            $resultVariable = $this->AliasResultVariable();
2364 2
            $expr->fieldIdentificationVariable = $resultVariable;
2365
2366
            // Include AliasResultVariable in query components.
2367 2
            $this->queryComponents[$resultVariable] = [
2368 2
                'resultvariable' => $expr,
2369 2
                'nestingLevel'   => $this->nestingLevel,
2370 2
                'token'          => $token,
2371
            ];
2372
        }
2373
2374 27
        return $expr;
2375
    }
2376
2377
    /**
2378
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2379
     *
2380
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2381
     */
2382 383 View Code Duplication
    public function ConditionalExpression()
2383
    {
2384 383
        $conditionalTerms = [];
2385 383
        $conditionalTerms[] = $this->ConditionalTerm();
2386
2387 380
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2388 16
            $this->match(Lexer::T_OR);
2389
2390 16
            $conditionalTerms[] = $this->ConditionalTerm();
2391
        }
2392
2393
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2394
        // if only one AST\ConditionalTerm is defined
2395 380
        if (count($conditionalTerms) == 1) {
2396 372
            return $conditionalTerms[0];
2397
        }
2398
2399 16
        return new AST\ConditionalExpression($conditionalTerms);
2400
    }
2401
2402
    /**
2403
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2404
     *
2405
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2406
     */
2407 383 View Code Duplication
    public function ConditionalTerm()
2408
    {
2409 383
        $conditionalFactors = [];
2410 383
        $conditionalFactors[] = $this->ConditionalFactor();
2411
2412 380
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2413 32
            $this->match(Lexer::T_AND);
2414
2415 32
            $conditionalFactors[] = $this->ConditionalFactor();
2416
        }
2417
2418
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2419
        // if only one AST\ConditionalFactor is defined
2420 380
        if (count($conditionalFactors) == 1) {
2421 362
            return $conditionalFactors[0];
2422
        }
2423
2424 32
        return new AST\ConditionalTerm($conditionalFactors);
2425
    }
2426
2427
    /**
2428
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2429
     *
2430
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2431
     */
2432 383
    public function ConditionalFactor()
2433
    {
2434 383
        $not = false;
2435
2436 383
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2437 6
            $this->match(Lexer::T_NOT);
2438
2439 6
            $not = true;
2440
        }
2441
2442 383
        $conditionalPrimary = $this->ConditionalPrimary();
2443
2444
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2445
        // if only one AST\ConditionalPrimary is defined
2446 380
        if ( ! $not) {
2447 378
            return $conditionalPrimary;
2448
        }
2449
2450 6
        $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary);
2451 6
        $conditionalFactor->not = $not;
2452
2453 6
        return $conditionalFactor;
2454
    }
2455
2456
    /**
2457
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
2458
     *
2459
     * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
2460
     */
2461 383
    public function ConditionalPrimary()
2462
    {
2463 383
        $condPrimary = new AST\ConditionalPrimary;
2464
2465 383
        if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2466 374
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2467
2468 371
            return $condPrimary;
2469
        }
2470
2471
        // Peek beyond the matching closing parenthesis ')'
2472 25
        $peek = $this->peekBeyondClosingParenthesis();
2473
2474 25
        if (in_array($peek['value'], ["=",  "<", "<=", "<>", ">", ">=", "!="]) ||
2475 22
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
2476 25
            $this->isMathOperator($peek)) {
2477 15
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2478
2479 15
            return $condPrimary;
2480
        }
2481
2482 21
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2483 21
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2484 21
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2485
2486 21
        return $condPrimary;
2487
    }
2488
2489
    /**
2490
     * SimpleConditionalExpression ::=
2491
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2492
     *      InExpression | NullComparisonExpression | ExistsExpression |
2493
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2494
     *      InstanceOfExpression
2495
     */
2496 383
    public function SimpleConditionalExpression()
2497
    {
2498 383
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2499 7
            return $this->ExistsExpression();
2500
        }
2501
2502 383
        $token      = $this->lexer->lookahead;
2503 383
        $peek       = $this->lexer->glimpse();
2504 383
        $lookahead  = $token;
2505
2506 383
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2507
            $token = $this->lexer->glimpse();
2508
        }
2509
2510 383
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2511
            // Peek beyond the matching closing parenthesis.
2512 359
            $beyond = $this->lexer->peek();
2513
2514 359
            switch ($peek['value']) {
2515 359
                case '(':
2516
                    // Peeks beyond the matched closing parenthesis.
2517 33
                    $token = $this->peekBeyondClosingParenthesis(false);
2518
2519 33
                    if ($token['type'] === Lexer::T_NOT) {
2520 3
                        $token = $this->lexer->peek();
2521
                    }
2522
2523 33
                    if ($token['type'] === Lexer::T_IS) {
2524 2
                        $lookahead = $this->lexer->peek();
2525
                    }
2526 33
                    break;
2527
2528
                default:
2529
                    // Peek beyond the PathExpression or InputParameter.
2530 332
                    $token = $beyond;
2531
2532 332
                    while ($token['value'] === '.') {
2533 288
                        $this->lexer->peek();
2534
2535 288
                        $token = $this->lexer->peek();
2536
                    }
2537
2538
                    // Also peek beyond a NOT if there is one.
2539 332
                    if ($token['type'] === Lexer::T_NOT) {
2540 11
                        $token = $this->lexer->peek();
2541
                    }
2542
2543
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2544 332
                    $lookahead = $this->lexer->peek();
2545
            }
2546
2547
            // Also peek beyond a NOT if there is one.
2548 359
            if ($lookahead['type'] === Lexer::T_NOT) {
2549 7
                $lookahead = $this->lexer->peek();
2550
            }
2551
2552 359
            $this->lexer->resetPeek();
2553
        }
2554
2555 383
        if ($token['type'] === Lexer::T_BETWEEN) {
2556 8
            return $this->BetweenExpression();
2557
        }
2558
2559 377
        if ($token['type'] === Lexer::T_LIKE) {
2560 14
            return $this->LikeExpression();
2561
        }
2562
2563 364
        if ($token['type'] === Lexer::T_IN) {
2564 35
            return $this->InExpression();
2565
        }
2566
2567 338
        if ($token['type'] === Lexer::T_INSTANCE) {
2568 17
            return $this->InstanceOfExpression();
2569
        }
2570
2571 321
        if ($token['type'] === Lexer::T_MEMBER) {
2572 7
            return $this->CollectionMemberExpression();
2573
        }
2574
2575 314
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2576 14
            return $this->NullComparisonExpression();
2577
        }
2578
2579 303
        if ($token['type'] === Lexer::T_IS  && $lookahead['type'] === Lexer::T_EMPTY) {
2580 4
            return $this->EmptyCollectionComparisonExpression();
2581
        }
2582
2583 299
        return $this->ComparisonExpression();
2584
    }
2585
2586
    /**
2587
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2588
     *
2589
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2590
     */
2591 4 View Code Duplication
    public function EmptyCollectionComparisonExpression()
2592
    {
2593 4
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2594 4
            $this->CollectionValuedPathExpression()
2595
        );
2596 4
        $this->match(Lexer::T_IS);
2597
2598 4
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2599 2
            $this->match(Lexer::T_NOT);
2600 2
            $emptyCollectionCompExpr->not = true;
2601
        }
2602
2603 4
        $this->match(Lexer::T_EMPTY);
2604
2605 4
        return $emptyCollectionCompExpr;
2606
    }
2607
2608
    /**
2609
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2610
     *
2611
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2612
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2613
     *
2614
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2615
     */
2616 7 View Code Duplication
    public function CollectionMemberExpression()
2617
    {
2618 7
        $not        = false;
2619 7
        $entityExpr = $this->EntityExpression();
2620
2621 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2622
            $this->match(Lexer::T_NOT);
2623
2624
            $not = true;
2625
        }
2626
2627 7
        $this->match(Lexer::T_MEMBER);
2628
2629 7
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2630 7
            $this->match(Lexer::T_OF);
2631
        }
2632
2633 7
        $collMemberExpr = new AST\CollectionMemberExpression(
2634 7
            $entityExpr, $this->CollectionValuedPathExpression()
2635
        );
2636 7
        $collMemberExpr->not = $not;
2637
2638 7
        return $collMemberExpr;
2639
    }
2640
2641
    /**
2642
     * Literal ::= string | char | integer | float | boolean
2643
     *
2644
     * @return \Doctrine\ORM\Query\AST\Literal
2645
     */
2646 177
    public function Literal()
2647
    {
2648 177
        switch ($this->lexer->lookahead['type']) {
2649 177 View Code Duplication
            case Lexer::T_STRING:
2650 48
                $this->match(Lexer::T_STRING);
2651
2652 48
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2653 137
            case Lexer::T_INTEGER:
2654 9 View Code Duplication
            case Lexer::T_FLOAT:
2655 129
                $this->match(
2656 129
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2657
                );
2658
2659 129
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2660 8
            case Lexer::T_TRUE:
2661 4 View Code Duplication
            case Lexer::T_FALSE:
2662 8
                $this->match(
2663 8
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2664
                );
2665
2666 8
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2667
            default:
2668
                $this->syntaxError('Literal');
2669
        }
2670
    }
2671
2672
    /**
2673
     * InParameter ::= Literal | InputParameter
2674
     *
2675
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2676
     */
2677 26
    public function InParameter()
2678
    {
2679 26
        if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2680 14
            return $this->InputParameter();
2681
        }
2682
2683 12
        return $this->Literal();
2684
    }
2685
2686
    /**
2687
     * InputParameter ::= PositionalParameter | NamedParameter
2688
     *
2689
     * @return \Doctrine\ORM\Query\AST\InputParameter
2690
     */
2691 167
    public function InputParameter()
2692
    {
2693 167
        $this->match(Lexer::T_INPUT_PARAMETER);
2694
2695 167
        return new AST\InputParameter($this->lexer->token['value']);
2696
    }
2697
2698
    /**
2699
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2700
     *
2701
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2702
     */
2703 333
    public function ArithmeticExpression()
2704
    {
2705 333
        $expr = new AST\ArithmeticExpression;
2706
2707 333
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2708 19
            $peek = $this->lexer->glimpse();
2709
2710 19
            if ($peek['type'] === Lexer::T_SELECT) {
2711 7
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2712 7
                $expr->subselect = $this->Subselect();
2713 7
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2714
2715 7
                return $expr;
2716
            }
2717
        }
2718
2719 333
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2720
2721 333
        return $expr;
2722
    }
2723
2724
    /**
2725
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2726
     *
2727
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2728
     */
2729 437 View Code Duplication
    public function SimpleArithmeticExpression()
2730
    {
2731 437
        $terms = [];
2732 437
        $terms[] = $this->ArithmeticTerm();
2733
2734 437
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2735 21
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2736
2737 21
            $terms[] = $this->lexer->token['value'];
2738 21
            $terms[] = $this->ArithmeticTerm();
2739
        }
2740
2741
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2742
        // if only one AST\ArithmeticTerm is defined
2743 437
        if (count($terms) == 1) {
2744 432
            return $terms[0];
2745
        }
2746
2747 21
        return new AST\SimpleArithmeticExpression($terms);
2748
    }
2749
2750
    /**
2751
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2752
     *
2753
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2754
     */
2755 437 View Code Duplication
    public function ArithmeticTerm()
2756
    {
2757 437
        $factors = [];
2758 437
        $factors[] = $this->ArithmeticFactor();
2759
2760 437
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2761 53
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2762
2763 53
            $factors[] = $this->lexer->token['value'];
2764 53
            $factors[] = $this->ArithmeticFactor();
2765
        }
2766
2767
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2768
        // if only one AST\ArithmeticFactor is defined
2769 437
        if (count($factors) == 1) {
2770 409
            return $factors[0];
2771
        }
2772
2773 53
        return new AST\ArithmeticTerm($factors);
2774
    }
2775
2776
    /**
2777
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2778
     *
2779
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2780
     */
2781 437
    public function ArithmeticFactor()
2782
    {
2783 437
        $sign = null;
2784
2785 437
        if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2786 3
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2787 3
            $sign = $isPlus;
2788
        }
2789
2790 437
        $primary = $this->ArithmeticPrimary();
2791
2792
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2793
        // if only one AST\ArithmeticPrimary is defined
2794 437
        if ($sign === null) {
2795 436
            return $primary;
2796
        }
2797
2798 3
        return new AST\ArithmeticFactor($primary, $sign);
2799
    }
2800
2801
    /**
2802
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2803
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2804
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2805
     *          | InputParameter | CaseExpression
2806
     */
2807 441
    public function ArithmeticPrimary()
2808
    {
2809 441 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2810 25
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2811
2812 25
            $expr = $this->SimpleArithmeticExpression();
2813
2814 25
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2815
2816 25
            return new AST\ParenthesisExpression($expr);
2817
        }
2818
2819 441
        switch ($this->lexer->lookahead['type']) {
2820 441
            case Lexer::T_COALESCE:
2821 441
            case Lexer::T_NULLIF:
2822 441
            case Lexer::T_CASE:
2823 4
                return $this->CaseExpression();
2824
2825 441
            case Lexer::T_IDENTIFIER:
2826 411
                $peek = $this->lexer->glimpse();
2827
2828 411
                if ($peek['value'] == '(') {
2829 28
                    return $this->FunctionDeclaration();
2830
                }
2831
2832 391
                if ($peek['value'] == '.') {
2833 380
                    return $this->SingleValuedPathExpression();
2834
                }
2835
2836 46
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2837 10
                    return $this->ResultVariable();
2838
                }
2839
2840 38
                return $this->StateFieldPathExpression();
2841
2842 311
            case Lexer::T_INPUT_PARAMETER:
2843 148
                return $this->InputParameter();
2844
2845
            default:
2846 171
                $peek = $this->lexer->glimpse();
2847
2848 171
                if ($peek['value'] == '(') {
2849 18
                    return $this->FunctionDeclaration();
2850
                }
2851
2852 167
                return $this->Literal();
2853
        }
2854
    }
2855
2856
    /**
2857
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2858
     *
2859
     * @return \Doctrine\ORM\Query\AST\Subselect |
2860
     *         string
2861
     */
2862 14
    public function StringExpression()
2863
    {
2864 14
        $peek = $this->lexer->glimpse();
2865
2866
        // Subselect
2867 14 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
2868
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2869
            $expr = $this->Subselect();
2870
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2871
2872
            return $expr;
2873
        }
2874
2875
        // ResultVariable (string)
2876 14
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2877 14
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2878 2
            return $this->ResultVariable();
2879
        }
2880
2881 12
        return $this->StringPrimary();
2882
    }
2883
2884
    /**
2885
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2886
     */
2887 51
    public function StringPrimary()
2888
    {
2889 51
        $lookaheadType = $this->lexer->lookahead['type'];
2890
2891
        switch ($lookaheadType) {
2892 51
            case Lexer::T_IDENTIFIER:
2893 32
                $peek = $this->lexer->glimpse();
2894
2895 32
                if ($peek['value'] == '.') {
2896 32
                    return $this->StateFieldPathExpression();
2897
                }
2898
2899 8
                if ($peek['value'] == '(') {
2900
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2901 8
                    return $this->FunctionDeclaration();
2902
                }
2903
2904
                $this->syntaxError("'.' or '('");
2905
                break;
2906
2907 32 View Code Duplication
            case Lexer::T_STRING:
2908 32
                $this->match(Lexer::T_STRING);
2909
2910 32
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2911
2912 2
            case Lexer::T_INPUT_PARAMETER:
2913 2
                return $this->InputParameter();
2914
2915
            case Lexer::T_CASE:
2916
            case Lexer::T_COALESCE:
2917
            case Lexer::T_NULLIF:
2918
                return $this->CaseExpression();
2919
        }
2920
2921
        $this->syntaxError(
2922
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2923
        );
2924
    }
2925
2926
    /**
2927
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2928
     *
2929
     * @return \Doctrine\ORM\Query\AST\PathExpression |
2930
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2931
     */
2932 7
    public function EntityExpression()
2933
    {
2934 7
        $glimpse = $this->lexer->glimpse();
2935
2936 7
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2937 1
            return $this->SingleValuedAssociationPathExpression();
2938
        }
2939
2940 6
        return $this->SimpleEntityExpression();
2941
    }
2942
2943
    /**
2944
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2945
     *
2946
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2947
     */
2948 6
    public function SimpleEntityExpression()
2949
    {
2950 6
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2951 5
            return $this->InputParameter();
2952
        }
2953
2954 1
        return $this->StateFieldPathExpression();
2955
    }
2956
2957
    /**
2958
     * AggregateExpression ::=
2959
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2960
     *
2961
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2962
     */
2963 85
    public function AggregateExpression()
2964
    {
2965 85
        $lookaheadType = $this->lexer->lookahead['type'];
2966 85
        $isDistinct = false;
2967
2968 85
        if ( ! in_array($lookaheadType, [Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM])) {
2969
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2970
        }
2971
2972 85
        $this->match($lookaheadType);
2973 85
        $functionName = $this->lexer->token['value'];
2974 85
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2975
2976 85
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2977 3
            $this->match(Lexer::T_DISTINCT);
2978 3
            $isDistinct = true;
2979
        }
2980
2981 85
        $pathExp = $this->SimpleArithmeticExpression();
2982
2983 85
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2984
2985 85
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
2986
    }
2987
2988
    /**
2989
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2990
     *
2991
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2992
     */
2993 3
    public function QuantifiedExpression()
2994
    {
2995 3
        $lookaheadType = $this->lexer->lookahead['type'];
2996 3
        $value = $this->lexer->lookahead['value'];
2997
2998 3
        if ( ! in_array($lookaheadType, [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME])) {
2999
            $this->syntaxError('ALL, ANY or SOME');
3000
        }
3001
3002 3
        $this->match($lookaheadType);
3003 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3004
3005 3
        $qExpr = new AST\QuantifiedExpression($this->Subselect());
3006 3
        $qExpr->type = $value;
3007
3008 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3009
3010 3
        return $qExpr;
3011
    }
3012
3013
    /**
3014
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
3015
     *
3016
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
3017
     */
3018 8
    public function BetweenExpression()
3019
    {
3020 8
        $not = false;
3021 8
        $arithExpr1 = $this->ArithmeticExpression();
3022
3023 8
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3024 3
            $this->match(Lexer::T_NOT);
3025 3
            $not = true;
3026
        }
3027
3028 8
        $this->match(Lexer::T_BETWEEN);
3029 8
        $arithExpr2 = $this->ArithmeticExpression();
3030 8
        $this->match(Lexer::T_AND);
3031 8
        $arithExpr3 = $this->ArithmeticExpression();
3032
3033 8
        $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
3034 8
        $betweenExpr->not = $not;
3035
3036 8
        return $betweenExpr;
3037
    }
3038
3039
    /**
3040
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
3041
     *
3042
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
3043
     */
3044 299
    public function ComparisonExpression()
3045
    {
3046 299
        $this->lexer->glimpse();
3047
3048 299
        $leftExpr  = $this->ArithmeticExpression();
3049 299
        $operator  = $this->ComparisonOperator();
3050 299
        $rightExpr = ($this->isNextAllAnySome())
3051 3
            ? $this->QuantifiedExpression()
3052 299
            : $this->ArithmeticExpression();
3053
3054 297
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
3055
    }
3056
3057
    /**
3058
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
3059
     *
3060
     * @return \Doctrine\ORM\Query\AST\InExpression
3061
     */
3062 35
    public function InExpression()
3063
    {
3064 35
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3065
3066 35
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3067 6
            $this->match(Lexer::T_NOT);
3068 6
            $inExpression->not = true;
3069
        }
3070
3071 35
        $this->match(Lexer::T_IN);
3072 35
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3073
3074 35
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3075 9
            $inExpression->subselect = $this->Subselect();
3076
        } else {
3077 26
            $literals = [];
3078 26
            $literals[] = $this->InParameter();
3079
3080 26
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3081 16
                $this->match(Lexer::T_COMMA);
3082 16
                $literals[] = $this->InParameter();
3083
            }
3084
3085 26
            $inExpression->literals = $literals;
3086
        }
3087
3088 34
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3089
3090 34
        return $inExpression;
3091
    }
3092
3093
    /**
3094
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3095
     *
3096
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3097
     */
3098 17
    public function InstanceOfExpression()
3099
    {
3100 17
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3101
3102 17
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3103 1
            $this->match(Lexer::T_NOT);
3104 1
            $instanceOfExpression->not = true;
3105
        }
3106
3107 17
        $this->match(Lexer::T_INSTANCE);
3108 17
        $this->match(Lexer::T_OF);
3109
3110 17
        $exprValues = [];
3111
3112 17
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3113 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3114
3115 2
            $exprValues[] = $this->InstanceOfParameter();
3116
3117 2
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3118 2
                $this->match(Lexer::T_COMMA);
3119
3120 2
                $exprValues[] = $this->InstanceOfParameter();
3121
            }
3122
3123 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3124
3125 2
            $instanceOfExpression->value = $exprValues;
3126
3127 2
            return $instanceOfExpression;
3128
        }
3129
3130 15
        $exprValues[] = $this->InstanceOfParameter();
3131
3132 15
        $instanceOfExpression->value = $exprValues;
3133
3134 15
        return $instanceOfExpression;
3135
    }
3136
3137
    /**
3138
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3139
     *
3140
     * @return mixed
3141
     */
3142 17
    public function InstanceOfParameter()
3143
    {
3144 17 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3145 6
            $this->match(Lexer::T_INPUT_PARAMETER);
3146
3147 6
            return new AST\InputParameter($this->lexer->token['value']);
3148
        }
3149
3150 11
        $abstractSchemaName = $this->AbstractSchemaName();
3151
3152 11
        $this->validateAbstractSchemaName($abstractSchemaName);
3153
3154 11
        return $abstractSchemaName;
3155
    }
3156
3157
    /**
3158
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
3159
     *
3160
     * @return \Doctrine\ORM\Query\AST\LikeExpression
3161
     */
3162 14
    public function LikeExpression()
3163
    {
3164 14
        $stringExpr = $this->StringExpression();
3165 14
        $not = false;
3166
3167 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3168 3
            $this->match(Lexer::T_NOT);
3169 3
            $not = true;
3170
        }
3171
3172 14
        $this->match(Lexer::T_LIKE);
3173
3174 14
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3175 4
            $this->match(Lexer::T_INPUT_PARAMETER);
3176 4
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3177
        } else {
3178 11
            $stringPattern = $this->StringPrimary();
3179
        }
3180
3181 14
        $escapeChar = null;
3182
3183 14
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
3184 2
            $this->match(Lexer::T_ESCAPE);
3185 2
            $this->match(Lexer::T_STRING);
3186
3187 2
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
3188
        }
3189
3190 14
        $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
3191 14
        $likeExpr->not = $not;
3192
3193 14
        return $likeExpr;
3194
    }
3195
3196
    /**
3197
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3198
     *
3199
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3200
     */
3201 14
    public function NullComparisonExpression()
3202
    {
3203
        switch (true) {
3204 14
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3205
                $this->match(Lexer::T_INPUT_PARAMETER);
3206
3207
                $expr = new AST\InputParameter($this->lexer->token['value']);
3208
                break;
3209
3210 14
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3211 1
                $expr = $this->NullIfExpression();
3212 1
                break;
3213
3214 14
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3215 1
                $expr = $this->CoalesceExpression();
3216 1
                break;
3217
3218 14
            case $this->isFunction():
3219 2
                $expr = $this->FunctionDeclaration();
3220 2
                break;
3221
3222
            default:
3223
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3224 13
                $glimpse = $this->lexer->glimpse();
3225
3226 13
                if ($glimpse['type'] === Lexer::T_DOT) {
3227 9
                    $expr = $this->SingleValuedPathExpression();
3228
3229
                    // Leave switch statement
3230 9
                    break;
3231
                }
3232
3233 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3234
3235
                // Validate existing component
3236 4
                if ( ! isset($this->queryComponents[$lookaheadValue])) {
3237
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3238
                }
3239
3240
                // Validate SingleValuedPathExpression (ie.: "product")
3241 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3242 1
                    $expr = $this->SingleValuedPathExpression();
3243 1
                    break;
3244
                }
3245
3246
                // Validating ResultVariable
3247 3
                if ( ! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3248
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3249
                }
3250
3251 3
                $expr = $this->ResultVariable();
3252 3
                break;
3253
        }
3254
3255 14
        $nullCompExpr = new AST\NullComparisonExpression($expr);
3256
3257 14
        $this->match(Lexer::T_IS);
3258
3259 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3260 5
            $this->match(Lexer::T_NOT);
3261
3262 5
            $nullCompExpr->not = true;
3263
        }
3264
3265 14
        $this->match(Lexer::T_NULL);
3266
3267 14
        return $nullCompExpr;
3268
    }
3269
3270
    /**
3271
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
3272
     *
3273
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
3274
     */
3275 7 View Code Duplication
    public function ExistsExpression()
3276
    {
3277 7
        $not = false;
3278
3279 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3280
            $this->match(Lexer::T_NOT);
3281
            $not = true;
3282
        }
3283
3284 7
        $this->match(Lexer::T_EXISTS);
3285 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3286
3287 7
        $existsExpression = new AST\ExistsExpression($this->Subselect());
3288 7
        $existsExpression->not = $not;
3289
3290 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3291
3292 7
        return $existsExpression;
3293
    }
3294
3295
    /**
3296
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
3297
     *
3298
     * @return string
3299
     */
3300 299
    public function ComparisonOperator()
3301
    {
3302 299
        switch ($this->lexer->lookahead['value']) {
3303 299
            case '=':
3304 248
                $this->match(Lexer::T_EQUALS);
3305
3306 248
                return '=';
3307
3308 62
            case '<':
3309 17
                $this->match(Lexer::T_LOWER_THAN);
3310 17
                $operator = '<';
3311
3312 17
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3313 5
                    $this->match(Lexer::T_EQUALS);
3314 5
                    $operator .= '=';
3315 12
                } else if ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3316 3
                    $this->match(Lexer::T_GREATER_THAN);
3317 3
                    $operator .= '>';
3318
                }
3319
3320 17
                return $operator;
3321
3322 53
            case '>':
3323 47
                $this->match(Lexer::T_GREATER_THAN);
3324 47
                $operator = '>';
3325
3326 47
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3327 6
                    $this->match(Lexer::T_EQUALS);
3328 6
                    $operator .= '=';
3329
                }
3330
3331 47
                return $operator;
3332
3333 6
            case '!':
3334 6
                $this->match(Lexer::T_NEGATE);
3335 6
                $this->match(Lexer::T_EQUALS);
3336
3337 6
                return '<>';
3338
3339
            default:
3340
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
3341
        }
3342
    }
3343
3344
    /**
3345
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
3346
     *
3347
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3348
     */
3349 141
    public function FunctionDeclaration()
3350
    {
3351 141
        $token = $this->lexer->lookahead;
3352 141
        $funcName = strtolower($token['value']);
3353
3354 141
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3355
3356
        // Check for custom functions functions first!
3357
        switch (true) {
3358 141
            case $customFunctionDeclaration !== null:
3359 4
                return $customFunctionDeclaration;
3360
            
3361 137
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3362 30
                return $this->FunctionsReturningStrings();
3363
3364 112
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3365 106
                return $this->FunctionsReturningNumerics();
3366
3367 7
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3368 7
                return $this->FunctionsReturningDatetime();
3369
3370
            default:
3371
                $this->syntaxError('known function', $token);
3372
        }
3373
    }
3374
3375
    /**
3376
     * Helper function for FunctionDeclaration grammar rule.
3377
     *
3378
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3379
     */
3380 141
    private function CustomFunctionDeclaration()
3381
    {
3382 141
        $token = $this->lexer->lookahead;
3383 141
        $funcName = strtolower($token['value']);
3384
3385
        // Check for custom functions afterwards
3386 141
        $config = $this->em->getConfiguration();
3387
3388
        switch (true) {
3389 141
            case ($config->getCustomStringFunction($funcName) !== null):
3390 3
                return $this->CustomFunctionsReturningStrings();
3391
3392 139
            case ($config->getCustomNumericFunction($funcName) !== null):
3393 2
                return $this->CustomFunctionsReturningNumerics();
3394
3395 137
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3396
                return $this->CustomFunctionsReturningDatetime();
3397
3398
            default:
3399 137
                return null;
3400
        }
3401
    }
3402
3403
    /**
3404
     * FunctionsReturningNumerics ::=
3405
     *      "LENGTH" "(" StringPrimary ")" |
3406
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
3407
     *      "ABS" "(" SimpleArithmeticExpression ")" |
3408
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
3409
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3410
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
3411
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3412
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
3413
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
3414
     *
3415
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3416
     */
3417 106 View Code Duplication
    public function FunctionsReturningNumerics()
3418
    {
3419 106
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3420 106
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3421
3422 106
        $function = new $funcClass($funcNameLower);
3423 106
        $function->parse($this);
3424
3425 106
        return $function;
3426
    }
3427
3428
    /**
3429
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3430
     */
3431 2 View Code Duplication
    public function CustomFunctionsReturningNumerics()
3432
    {
3433
        // getCustomNumericFunction is case-insensitive
3434 2
        $functionName  = strtolower($this->lexer->lookahead['value']);
3435 2
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3436
3437 2
        $function = is_string($functionClass)
3438 1
            ? new $functionClass($functionName)
3439 2
            : call_user_func($functionClass, $functionName);
3440
3441 2
        $function->parse($this);
3442
3443 2
        return $function;
3444
    }
3445
3446
    /**
3447
     * FunctionsReturningDateTime ::=
3448
     *     "CURRENT_DATE" |
3449
     *     "CURRENT_TIME" |
3450
     *     "CURRENT_TIMESTAMP" |
3451
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
3452
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
3453
     *
3454
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3455
     */
3456 7 View Code Duplication
    public function FunctionsReturningDatetime()
3457
    {
3458 7
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3459 7
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3460
3461 7
        $function = new $funcClass($funcNameLower);
3462 7
        $function->parse($this);
3463
3464 7
        return $function;
3465
    }
3466
3467
    /**
3468
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3469
     */
3470 View Code Duplication
    public function CustomFunctionsReturningDatetime()
3471
    {
3472
        // getCustomDatetimeFunction is case-insensitive
3473
        $functionName  = $this->lexer->lookahead['value'];
3474
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
3475
3476
        $function = is_string($functionClass)
3477
            ? new $functionClass($functionName)
3478
            : call_user_func($functionClass, $functionName);
3479
3480
        $function->parse($this);
3481
3482
        return $function;
3483
    }
3484
3485
    /**
3486
     * FunctionsReturningStrings ::=
3487
     *   "CONCAT" "(" StringPrimary "," StringPrimary {"," StringPrimary}* ")" |
3488
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
3489
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
3490
     *   "LOWER" "(" StringPrimary ")" |
3491
     *   "UPPER" "(" StringPrimary ")" |
3492
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
3493
     *
3494
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3495
     */
3496 30 View Code Duplication
    public function FunctionsReturningStrings()
3497
    {
3498 30
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3499 30
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3500
3501 30
        $function = new $funcClass($funcNameLower);
3502 30
        $function->parse($this);
3503
3504 30
        return $function;
3505
    }
3506
3507
    /**
3508
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3509
     */
3510 3 View Code Duplication
    public function CustomFunctionsReturningStrings()
3511
    {
3512
        // getCustomStringFunction is case-insensitive
3513 3
        $functionName  = $this->lexer->lookahead['value'];
3514 3
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3515
3516 3
        $function = is_string($functionClass)
3517 2
            ? new $functionClass($functionName)
3518 3
            : call_user_func($functionClass, $functionName);
3519
3520 3
        $function->parse($this);
3521
3522 3
        return $function;
3523
    }
3524
}
3525