Passed
Pull Request — 2.6 (#8015)
by
unknown
08:26
created

Parser::PartialObjectExpression()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 4.0176

Importance

Changes 0
Metric Value
eloc 28
dl 0
loc 50
ccs 26
cts 29
cp 0.8966
rs 9.472
c 0
b 0
f 0
cc 4
nc 6
nop 0
crap 4.0176
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\Query\AST\Functions;
25
use function in_array;
26
use function strpos;
27
28
/**
29
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
30
 * Parses a DQL query, reports any errors in it, and generates an AST.
31
 *
32
 * @since   2.0
33
 * @author  Guilherme Blanco <[email protected]>
34
 * @author  Jonathan Wage <[email protected]>
35
 * @author  Roman Borschel <[email protected]>
36
 * @author  Janne Vanhala <[email protected]>
37
 * @author  Fabio B. Silva <[email protected]>
38
 */
39
class Parser
40
{
41
    /**
42
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
43
     *
44
     * @var array
45
     */
46
    private static $_STRING_FUNCTIONS = [
47
        'concat'    => Functions\ConcatFunction::class,
48
        'substring' => Functions\SubstringFunction::class,
49
        'trim'      => Functions\TrimFunction::class,
50
        'lower'     => Functions\LowerFunction::class,
51
        'upper'     => Functions\UpperFunction::class,
52
        'identity'  => Functions\IdentityFunction::class,
53
    ];
54
55
    /**
56
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
57
     *
58
     * @var array
59
     */
60
    private static $_NUMERIC_FUNCTIONS = [
61
        'length'    => Functions\LengthFunction::class,
62
        'locate'    => Functions\LocateFunction::class,
63
        'abs'       => Functions\AbsFunction::class,
64
        'sqrt'      => Functions\SqrtFunction::class,
65
        'mod'       => Functions\ModFunction::class,
66
        'size'      => Functions\SizeFunction::class,
67
        'date_diff' => Functions\DateDiffFunction::class,
68
        'bit_and'   => Functions\BitAndFunction::class,
69
        'bit_or'    => Functions\BitOrFunction::class,
70
71
        // Aggregate functions
72
        'min'       => Functions\MinFunction::class,
73
        'max'       => Functions\MaxFunction::class,
74
        'avg'       => Functions\AvgFunction::class,
75
        'sum'       => Functions\SumFunction::class,
76
        'count'     => Functions\CountFunction::class,
77
    ];
78
79
    /**
80
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
81
     *
82
     * @var array
83
     */
84
    private static $_DATETIME_FUNCTIONS = [
85
        'current_date'      => Functions\CurrentDateFunction::class,
86
        'current_time'      => Functions\CurrentTimeFunction::class,
87
        'current_timestamp' => Functions\CurrentTimestampFunction::class,
88
        'date_add'          => Functions\DateAddFunction::class,
89
        'date_sub'          => Functions\DateSubFunction::class,
90
    ];
91
92
    /*
93
     * Expressions that were encountered during parsing of identifiers and expressions
94
     * and still need to be validated.
95
     */
96
97
    /**
98
     * @var array
99
     */
100
    private $deferredIdentificationVariables = [];
101
102
    /**
103
     * @var array
104
     */
105
    private $deferredPartialObjectExpressions = [];
106
107
    /**
108
     * @var array
109
     */
110
    private $deferredPathExpressions = [];
111
112
    /**
113
     * @var array
114
     */
115
    private $deferredResultVariables = [];
116
117
    /**
118
     * @var array
119
     */
120
    private $deferredNewObjectExpressions = [];
121
122
    /**
123
     * The lexer.
124
     *
125
     * @var \Doctrine\ORM\Query\Lexer
126
     */
127
    private $lexer;
128
129
    /**
130
     * The parser result.
131
     *
132
     * @var \Doctrine\ORM\Query\ParserResult
133
     */
134
    private $parserResult;
135
136
    /**
137
     * The EntityManager.
138
     *
139
     * @var \Doctrine\ORM\EntityManager
140
     */
141
    private $em;
142
143
    /**
144
     * The Query to parse.
145
     *
146
     * @var Query
147
     */
148
    private $query;
149
150
    /**
151
     * Map of declared query components in the parsed query.
152
     *
153
     * @var array
154
     */
155
    private $queryComponents = [];
156
157
    /**
158
     * Keeps the nesting level of defined ResultVariables.
159
     *
160
     * @var integer
161
     */
162
    private $nestingLevel = 0;
163
164
    /**
165
     * Any additional custom tree walkers that modify the AST.
166
     *
167
     * @var array
168
     */
169
    private $customTreeWalkers = [];
170
171
    /**
172
     * The custom last tree walker, if any, that is responsible for producing the output.
173
     *
174
     * @var TreeWalker
175
     */
176
    private $customOutputWalker;
177
178
    /**
179
     * @var array
180
     */
181
    private $identVariableExpressions = [];
182
183
    /**
184
     * Creates a new query parser object.
185
     *
186
     * @param Query $query The Query to parse.
187
     */
188 870
    public function __construct(Query $query)
189
    {
190 870
        $this->query        = $query;
191 870
        $this->em           = $query->getEntityManager();
192 870
        $this->lexer        = new Lexer($query->getDQL());
193 870
        $this->parserResult = new ParserResult();
194 870
    }
195
196
    /**
197
     * Sets a custom tree walker that produces output.
198
     * This tree walker will be run last over the AST, after any other walkers.
199
     *
200
     * @param string $className
201
     *
202
     * @return void
203
     */
204 128
    public function setCustomOutputTreeWalker($className)
205
    {
206 128
        $this->customOutputWalker = $className;
0 ignored issues
show
Documentation Bug introduced by
It seems like $className of type string is incompatible with the declared type Doctrine\ORM\Query\TreeWalker of property $customOutputWalker.

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

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

Loading history...
207 128
    }
208
209
    /**
210
     * Adds a custom tree walker for modifying the AST.
211
     *
212
     * @param string $className
213
     *
214
     * @return void
215
     */
216
    public function addCustomTreeWalker($className)
217
    {
218
        $this->customTreeWalkers[] = $className;
219
    }
220
221
    /**
222
     * Gets the lexer used by the parser.
223
     *
224
     * @return \Doctrine\ORM\Query\Lexer
225
     */
226 30
    public function getLexer()
227
    {
228 30
        return $this->lexer;
229
    }
230
231
    /**
232
     * Gets the ParserResult that is being filled with information during parsing.
233
     *
234
     * @return \Doctrine\ORM\Query\ParserResult
235
     */
236
    public function getParserResult()
237
    {
238
        return $this->parserResult;
239
    }
240
241
    /**
242
     * Gets the EntityManager used by the parser.
243
     *
244
     * @return \Doctrine\ORM\EntityManager
245
     */
246
    public function getEntityManager()
247
    {
248
        return $this->em;
249
    }
250
251
    /**
252
     * Parses and builds AST for the given Query.
253
     *
254
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
255
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
256
     *         \Doctrine\ORM\Query\AST\DeleteStatement
257
     */
258 870
    public function getAST()
259
    {
260
        // Parse & build AST
261 870
        $AST = $this->QueryLanguage();
262
263
        // Process any deferred validations of some nodes in the AST.
264
        // This also allows post-processing of the AST for modification purposes.
265 828
        $this->processDeferredIdentificationVariables();
266
267 826
        if ($this->deferredPartialObjectExpressions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredPartialObjectExpressions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
268 10
            $this->processDeferredPartialObjectExpressions();
269
        }
270
271 824
        if ($this->deferredPathExpressions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredPathExpressions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
272 608
            $this->processDeferredPathExpressions();
273
        }
274
275 821
        if ($this->deferredResultVariables) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredResultVariables of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
276 32
            $this->processDeferredResultVariables();
277
        }
278
279 821
        if ($this->deferredNewObjectExpressions) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->deferredNewObjectExpressions of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
280 28
            $this->processDeferredNewObjectExpressions($AST);
0 ignored issues
show
Bug introduced by
$AST of type Doctrine\ORM\Query\AST\SelectStatement is incompatible with the type Doctrine\ORM\Query\AST\SelectClause expected by parameter $AST of Doctrine\ORM\Query\Parse...dNewObjectExpressions(). ( Ignorable by Annotation )

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

280
            $this->processDeferredNewObjectExpressions(/** @scrutinizer ignore-type */ $AST);
Loading history...
281
        }
282
283 817
        $this->processRootEntityAliasSelected();
284
285
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
286 816
        $this->fixIdentificationVariableOrder($AST);
287
288 816
        return $AST;
289
    }
290
291
    /**
292
     * Attempts to match the given token with the current lookahead token.
293
     *
294
     * If they match, updates the lookahead token; otherwise raises a syntax
295
     * error.
296
     *
297
     * @param int $token The token type.
298
     *
299
     * @return void
300
     *
301
     * @throws QueryException If the tokens don't match.
302
     */
303 881
    public function match($token)
304
    {
305 881
        $lookaheadType = $this->lexer->lookahead['type'];
306
307
        // Short-circuit on first condition, usually types match
308 881
        if ($lookaheadType === $token) {
309 873
            $this->lexer->moveNext();
310 873
            return;
311
        }
312
313
        // If parameter is not identifier (1-99) must be exact match
314 21
        if ($token < Lexer::T_IDENTIFIER) {
315 3
            $this->syntaxError($this->lexer->getLiteral($token));
316
        }
317
318
        // If parameter is keyword (200+) must be exact match
319 18
        if ($token > Lexer::T_IDENTIFIER) {
320 7
            $this->syntaxError($this->lexer->getLiteral($token));
321
        }
322
323
        // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
324 11
        if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
325 8
            $this->syntaxError($this->lexer->getLiteral($token));
326
        }
327
328 3
        $this->lexer->moveNext();
329 3
    }
330
331
    /**
332
     * Frees this parser, enabling it to be reused.
333
     *
334
     * @param boolean $deep     Whether to clean peek and reset errors.
335
     * @param integer $position Position to reset.
336
     *
337
     * @return void
338
     */
339
    public function free($deep = false, $position = 0)
340
    {
341
        // WARNING! Use this method with care. It resets the scanner!
342
        $this->lexer->resetPosition($position);
343
344
        // Deep = true cleans peek and also any previously defined errors
345
        if ($deep) {
346
            $this->lexer->resetPeek();
347
        }
348
349
        $this->lexer->token = null;
350
        $this->lexer->lookahead = null;
351
    }
352
353
    /**
354
     * Parses a query string.
355
     *
356
     * @return ParserResult
357
     */
358 870
    public function parse()
359
    {
360 870
        $AST = $this->getAST();
361
362 816
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
363 113
            $this->customTreeWalkers = $customWalkers;
364
        }
365
366 816
        if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
367 80
            $this->customOutputWalker = $customOutputWalker;
368
        }
369
370
        // Run any custom tree walkers over the AST
371 816
        if ($this->customTreeWalkers) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->customTreeWalkers of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

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

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

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

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

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

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

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