Failed Conditions
Push — master ( a16dc6...b210c1 )
by Luís
09:00
created

lib/Doctrine/ORM/Query/Parser.php (21 issues)

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 854
    public function __construct(Query $query)
187
    {
188 854
        $this->query        = $query;
189 854
        $this->em           = $query->getEntityManager();
190 854
        $this->lexer        = new Lexer($query->getDQL());
191 854
        $this->parserResult = new ParserResult();
192 854
    }
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;
0 ignored issues
show
Documentation Bug introduced by
It seems like $className of type string is incompatible with the declared type Doctrine\ORM\Query\TreeWalker of property $customOutputWalker.

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

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

Loading history...
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 854
    public function getAST()
257
    {
258
        // Parse & build AST
259 854
        $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 812
        $this->processDeferredIdentificationVariables();
264
265 810
        if ($this->deferredPartialObjectExpressions) {
266 11
            $this->processDeferredPartialObjectExpressions();
267
        }
268
269 808
        if ($this->deferredPathExpressions) {
270 599
            $this->processDeferredPathExpressions();
271
        }
272
273 805
        if ($this->deferredResultVariables) {
274 32
            $this->processDeferredResultVariables();
275
        }
276
277 805
        if ($this->deferredNewObjectExpressions) {
278 28
            $this->processDeferredNewObjectExpressions($AST);
0 ignored issues
show
$AST of type Doctrine\ORM\Query\AST\SelectStatement is incompatible with the type Doctrine\ORM\Query\AST\SelectClause expected by parameter $AST of Doctrine\ORM\Query\Parse...dNewObjectExpressions(). ( Ignorable by Annotation )

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

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

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2287 612
            $this->identVariableExpressions[$identVariable] = $expr;
2288
        }
2289
2290 781
        return $expr;
2291
    }
2292
2293
    /**
2294
     * SimpleSelectExpression ::= (
2295
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2296
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2297
     * ) [["AS"] AliasResultVariable]
2298
     *
2299
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2300
     */
2301 49
    public function SimpleSelectExpression()
2302
    {
2303 49
        $peek = $this->lexer->glimpse();
2304
2305 49
        switch ($this->lexer->lookahead['type']) {
2306 49
            case Lexer::T_IDENTIFIER:
2307
                switch (true) {
2308 19
                    case ($peek['type'] === Lexer::T_DOT):
2309 16
                        $expression = $this->StateFieldPathExpression();
2310
2311 16
                        return new AST\SimpleSelectExpression($expression);
2312
2313 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2314 2
                        $expression = $this->IdentificationVariable();
2315
2316 2
                        return new AST\SimpleSelectExpression($expression);
0 ignored issues
show
$expression of type string is incompatible with the type Doctrine\ORM\Query\AST\Node expected by parameter $expression of Doctrine\ORM\Query\AST\S...pression::__construct(). ( Ignorable by Annotation )

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

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

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

3192
        $likeExpr = new AST\LikeExpression($stringExpr, /** @scrutinizer ignore-type */ $stringPattern, $escapeChar);
Loading history...
3193 14
        $likeExpr->not = $not;
3194
3195 14
        return $likeExpr;
3196
    }
3197
3198
    /**
3199
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
3200
     *
3201
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
3202
     */
3203 14
    public function NullComparisonExpression()
3204
    {
3205
        switch (true) {
3206 14
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
3207
                $this->match(Lexer::T_INPUT_PARAMETER);
3208
3209
                $expr = new AST\InputParameter($this->lexer->token['value']);
3210
                break;
3211
3212 14
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3213 1
                $expr = $this->NullIfExpression();
3214 1
                break;
3215
3216 14
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3217 1
                $expr = $this->CoalesceExpression();
3218 1
                break;
3219
3220 14
            case $this->isFunction():
3221 2
                $expr = $this->FunctionDeclaration();
3222 2
                break;
3223
3224
            default:
3225
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
3226 13
                $glimpse = $this->lexer->glimpse();
3227
3228 13
                if ($glimpse['type'] === Lexer::T_DOT) {
3229 9
                    $expr = $this->SingleValuedPathExpression();
3230
3231
                    // Leave switch statement
3232 9
                    break;
3233
                }
3234
3235 4
                $lookaheadValue = $this->lexer->lookahead['value'];
3236
3237
                // Validate existing component
3238 4
                if ( ! isset($this->queryComponents[$lookaheadValue])) {
3239
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3240
                }
3241
3242
                // Validate SingleValuedPathExpression (ie.: "product")
3243 4
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3244 1
                    $expr = $this->SingleValuedPathExpression();
3245 1
                    break;
3246
                }
3247
3248
                // Validating ResultVariable
3249 3
                if ( ! isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
3250
                    $this->semanticalError('Cannot add having condition on a non result variable.');
3251
                }
3252
3253 3
                $expr = $this->ResultVariable();
3254 3
                break;
3255
        }
3256
3257 14
        $nullCompExpr = new AST\NullComparisonExpression($expr);
0 ignored issues
show
It seems like $expr can also be of type string; however, parameter $expression of Doctrine\ORM\Query\AST\N...pression::__construct() does only seem to accept Doctrine\ORM\Query\AST\Node, maybe add an additional type check? ( Ignorable by Annotation )

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

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