Failed Conditions
Pull Request — 2.7 (#8027)
by
unknown
07:00
created

Parser::ComparisonExpression()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 0
dl 0
loc 11
ccs 8
cts 8
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\Query\AST\Functions;
25
use function 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 520
    public function __construct(Query $query)
189
    {
190 520
        $this->query        = $query;
191 520
        $this->em           = $query->getEntityManager();
192 520
        $this->lexer        = new Lexer((string) $query->getDQL());
193 520
        $this->parserResult = new ParserResult();
194 520
    }
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 129
    public function setCustomOutputTreeWalker($className)
205
    {
206 129
        $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 129
    }
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 22
    public function getLexer()
227
    {
228 22
        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 519
    public function getAST()
259
    {
260
        // Parse & build AST
261 519
        $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 475
        $this->processDeferredIdentificationVariables();
266
267 473
        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 7
            $this->processDeferredPartialObjectExpressions();
269
        }
270
271 471
        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 359
            $this->processDeferredPathExpressions();
273
        }
274
275 468
        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 27
            $this->processDeferredResultVariables();
277
        }
278
279 468
        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 7
            $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 464
        $this->processRootEntityAliasSelected();
284
285
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
286 463
        $this->fixIdentificationVariableOrder($AST);
287
288 463
        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 529
    public function match($token)
304
    {
305 529
        $lookaheadType = $this->lexer->lookahead['type'] ?? null;
306
307
        // Short-circuit on first condition, usually types match
308 529
        if ($lookaheadType === $token) {
309 520
            $this->lexer->moveNext();
310 520
            return;
311
        }
312
313
        // If parameter is not identifier (1-99) must be exact match
314 22
        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 19
        if ($token > Lexer::T_IDENTIFIER) {
320 8
            $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 519
    public function parse()
359
    {
360 519
        $AST = $this->getAST();
361
362 463
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
363 45
            $this->customTreeWalkers = $customWalkers;
364
        }
365
366 463
        if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
367 38
            $this->customOutputWalker = $customOutputWalker;
368
        }
369
370
        // Run any custom tree walkers over the AST
371 463
        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 44
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
373
374 44
            foreach ($this->customTreeWalkers as $walker) {
375 44
                $treeWalkerChain->addTreeWalker($walker);
376
            }
377
378
            switch (true) {
379 44
                case ($AST instanceof AST\UpdateStatement):
380
                    $treeWalkerChain->walkUpdateStatement($AST);
381
                    break;
382
383 44
                case ($AST instanceof AST\DeleteStatement):
384
                    $treeWalkerChain->walkDeleteStatement($AST);
385
                    break;
386
387 44
                case ($AST instanceof AST\SelectStatement):
388
                default:
389 44
                    $treeWalkerChain->walkSelectStatement($AST);
390
            }
391
392 38
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
393
        }
394
395 457
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
396 457
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
397
398
        // Assign an SQL executor to the parser result
399 457
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
400
401 450
        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 463
    private function fixIdentificationVariableOrder($AST)
416
    {
417 463
        if (count($this->identVariableExpressions) <= 1) {
418 397
            return;
419
        }
420
421 66
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
422 66
            if ( ! isset($this->identVariableExpressions[$dqlAlias])) {
423 7
                continue;
424
            }
425
426 66
            $expr = $this->identVariableExpressions[$dqlAlias];
427 66
            $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 66
            unset($AST->selectClause->selectExpressions[$key]);
430
431 66
            $AST->selectClause->selectExpressions[] = $expr;
432
        }
433 66
    }
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 21
    public function syntaxError($expected = '', $token = null)
446
    {
447 21
        if ($token === null) {
448 18
            $token = $this->lexer->lookahead;
449
        }
450
451 21
        $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
452
453 21
        $message  = "line 0, col {$tokenPos}: Error: ";
454 21
        $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected ';
455 21
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'";
456
457 21
        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 116
    private function peekBeyondClosingParenthesis($resetPeek = true)
503
    {
504 116
        $token = $this->lexer->peek();
505 116
        $numUnmatched = 1;
506
507 116
        while ($numUnmatched > 0 && $token !== null) {
508 115
            switch ($token['type']) {
509 115
                case Lexer::T_OPEN_PARENTHESIS:
510 24
                    ++$numUnmatched;
511 24
                    break;
512
513 115
                case Lexer::T_CLOSE_PARENTHESIS:
514 115
                    --$numUnmatched;
515 115
                    break;
516
517
                default:
518
                    // Do nothing
519
            }
520
521 115
            $token = $this->lexer->peek();
522
        }
523
524 116
        if ($resetPeek) {
525 96
            $this->lexer->resetPeek();
526
        }
527
528 116
        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 188
    private function isMathOperator($token)
539
    {
540 188
        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 230
    private function isFunction()
549
    {
550 230
        $lookaheadType = $this->lexer->lookahead['type'];
551 230
        $peek          = $this->lexer->peek();
552
553 230
        $this->lexer->resetPeek();
554
555 230
        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 2
    private function isAggregateFunction($tokenType)
566
    {
567 2
        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 205
    private function isNextAllAnySome()
576
    {
577 205
        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 475
    private function processDeferredIdentificationVariables()
587
    {
588 475
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
589 466
            $identVariable = $deferredItem['expression'];
590
591
            // Check if IdentificationVariable exists in queryComponents
592 466
            if ( ! isset($this->queryComponents[$identVariable])) {
593 1
                $this->semanticalError(
594 1
                    "'$identVariable' is not defined.", $deferredItem['token']
595
                );
596
            }
597
598 466
            $qComp = $this->queryComponents[$identVariable];
599
600
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
601 466
            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 466
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
609 1
                $this->semanticalError(
610 466
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
611
                );
612
            }
613
        }
614 473
    }
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 7
    private function processDeferredNewObjectExpressions($AST)
624
    {
625 7
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
626 7
            $expression     = $deferredItem['expression'];
627 7
            $token          = $deferredItem['token'];
628 7
            $className      = $expression->className;
629 7
            $args           = $expression->args;
630 7
            $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 7
                ? $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName
632 7
                : null;
633
634
            // If the namespace is not given then assumes the first FROM entity namespace
635 7
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
636
                $namespace  = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
637
                $fqcn       = $namespace . '\\' . $className;
638
639
                if (class_exists($fqcn)) {
640
                    $expression->className  = $fqcn;
641
                    $className              = $fqcn;
642
                }
643
            }
644
645 7
            if ( ! class_exists($className)) {
646 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
647
            }
648
649 6
            $class = new \ReflectionClass($className);
650
651 6
            if ( ! $class->isInstantiable()) {
652 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
653
            }
654
655 5
            if ($class->getConstructor() === null) {
656 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
657
            }
658
659 4
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
660 4
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
661
            }
662
        }
663 3
    }
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 7
    private function processDeferredPartialObjectExpressions()
672
    {
673 7
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
674 7
            $expr = $deferredItem['expression'];
675 7
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
676
677 7
            foreach ($expr->partialFieldSet as $field) {
678 7
                if (isset($class->fieldMappings[$field])) {
679 7
                    continue;
680
                }
681
682 2
                if (isset($class->associationMappings[$field]) &&
683 2
                    $class->associationMappings[$field]['isOwningSide'] &&
684 2
                    $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) {
685 1
                    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 6
            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 6
                    $deferredItem['token']
697
                );
698
            }
699
        }
700 5
    }
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 27
    private function processDeferredResultVariables()
709
    {
710 27
        foreach ($this->deferredResultVariables as $deferredItem) {
711 27
            $resultVariable = $deferredItem['expression'];
712
713
            // Check if ResultVariable exists in queryComponents
714 27
            if ( ! isset($this->queryComponents[$resultVariable])) {
715
                $this->semanticalError(
716
                    "'$resultVariable' is not defined.", $deferredItem['token']
717
                );
718
            }
719
720 27
            $qComp = $this->queryComponents[$resultVariable];
721
722
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
723 27
            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 27
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
731
                $this->semanticalError(
732 27
                    "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
733
                );
734
            }
735
        }
736 27
    }
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 359
    private function processDeferredPathExpressions()
750
    {
751 359
        foreach ($this->deferredPathExpressions as $deferredItem) {
752 359
            $pathExpression = $deferredItem['expression'];
753
754 359
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
755 359
            $class = $qComp['metadata'];
756
757 359
            if (($field = $pathExpression->field) === null) {
758 23
                $field = $pathExpression->field = $class->identifier[0];
759
            }
760
761
            // Check if field or association exists
762 359
            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 358
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
770
771 358
            if (isset($class->associationMappings[$field])) {
772 67
                $assoc = $class->associationMappings[$field];
773
774 67
                $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
775 47
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
776 67
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
777
            }
778
779
            // Validate if PathExpression is one of the expected types
780 358
            $expectedType = $pathExpression->expectedType;
781
782 358
            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 356
            $pathExpression->type = $fieldType;
812
        }
813 356
    }
814
815
    /**
816
     * @return void
817
     */
818 464
    private function processRootEntityAliasSelected()
819
    {
820 464
        if ( ! count($this->identVariableExpressions)) {
821 172
            return;
822
        }
823
824 297
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
825 297
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
826 297
                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 519
    public function QueryLanguage()
841
    {
842 519
        $statement = null;
843
844 519
        $this->lexer->moveNext();
845
846 519
        switch ($this->lexer->lookahead['type'] ?? null) {
847 519
            case Lexer::T_SELECT:
848 453
                $statement = $this->SelectStatement();
849 415
                break;
850
851 67
            case Lexer::T_UPDATE:
852 26
                $statement = $this->UpdateStatement();
853 26
                break;
854
855 41
            case Lexer::T_DELETE:
856 38
                $statement = $this->DeleteStatement();
857 38
                break;
858
859
            default:
860 4
                $this->syntaxError('SELECT, UPDATE or DELETE');
861
                break;
862
        }
863
864
        // Check for end of string
865 479
        if ($this->lexer->lookahead !== null) {
866 4
            $this->syntaxError('end of string');
867
        }
868
869 475
        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 453
    public function SelectStatement()
878
    {
879 453
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
880
881 419
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
882 416
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
883 415
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
884 415
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
885
886 415
        return $selectStatement;
887
    }
888
889
    /**
890
     * UpdateStatement ::= UpdateClause [WhereClause]
891
     *
892
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
893
     */
894 26
    public function UpdateStatement()
895
    {
896 26
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
897
898 26
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
899
900 26
        return $updateStatement;
901
    }
902
903
    /**
904
     * DeleteStatement ::= DeleteClause [WhereClause]
905
     *
906
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
907
     */
908 38
    public function DeleteStatement()
909
    {
910 38
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
911
912 38
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
913
914 38
        return $deleteStatement;
915
    }
916
917
    /**
918
     * IdentificationVariable ::= identifier
919
     *
920
     * @return string
921
     */
922 497
    public function IdentificationVariable()
923
    {
924 497
        $this->match(Lexer::T_IDENTIFIER);
925
926 497
        $identVariable = $this->lexer->token['value'];
927
928 497
        $this->deferredIdentificationVariables[] = [
929 497
            'expression'   => $identVariable,
930 497
            'nestingLevel' => $this->nestingLevel,
931 497
            'token'        => $this->lexer->token,
932
        ];
933
934 497
        return $identVariable;
935
    }
936
937
    /**
938
     * AliasIdentificationVariable = identifier
939
     *
940
     * @return string
941
     */
942 485
    public function AliasIdentificationVariable()
943
    {
944 485
        $this->match(Lexer::T_IDENTIFIER);
945
946 485
        $aliasIdentVariable = $this->lexer->token['value'];
947 485
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
948
949 485
        if ($exists) {
950 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
951
        }
952
953 485
        return $aliasIdentVariable;
954
    }
955
956
    /**
957
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
958
     *
959
     * @return string
960
     */
961 507
    public function AbstractSchemaName()
962
    {
963 507
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
964 491
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
965
966 491
            return $this->lexer->token['value'];
967
        }
968
969 17
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
970 8
            $this->match(Lexer::T_IDENTIFIER);
971
972 8
            return $this->lexer->token['value'];
973
        }
974
975 9
        $this->match(Lexer::T_ALIASED_NAME);
976
977 8
        [$namespaceAlias, $simpleClassName] = explode(':', $this->lexer->token['value']);
978
979 8
        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 501
    private function validateAbstractSchemaName($schemaName)
990
    {
991 501
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
992 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
993
        }
994 486
    }
995
996
    /**
997
     * AliasResultVariable ::= identifier
998
     *
999
     * @return string
1000
     */
1001 80
    public function AliasResultVariable()
1002
    {
1003 80
        $this->match(Lexer::T_IDENTIFIER);
1004
1005 76
        $resultVariable = $this->lexer->token['value'];
1006 76
        $exists = isset($this->queryComponents[$resultVariable]);
1007
1008 76
        if ($exists) {
1009 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1010
        }
1011
1012 76
        return $resultVariable;
1013
    }
1014
1015
    /**
1016
     * ResultVariable ::= identifier
1017
     *
1018
     * @return string
1019
     */
1020 27
    public function ResultVariable()
1021
    {
1022 27
        $this->match(Lexer::T_IDENTIFIER);
1023
1024 27
        $resultVariable = $this->lexer->token['value'];
1025
1026
        // Defer ResultVariable validation
1027 27
        $this->deferredResultVariables[] = [
1028 27
            'expression'   => $resultVariable,
1029 27
            'nestingLevel' => $this->nestingLevel,
1030 27
            'token'        => $this->lexer->token,
1031
        ];
1032
1033 27
        return $resultVariable;
1034
    }
1035
1036
    /**
1037
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1038
     *
1039
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1040
     */
1041 107
    public function JoinAssociationPathExpression()
1042
    {
1043 107
        $identVariable = $this->IdentificationVariable();
1044
1045 107
        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 107
        $this->match(Lexer::T_DOT);
1052 107
        $this->match(Lexer::T_IDENTIFIER);
1053
1054 107
        $field = $this->lexer->token['value'];
1055
1056
        // Validate association field
1057 107
        $qComp = $this->queryComponents[$identVariable];
1058 107
        $class = $qComp['metadata'];
1059
1060 107
        if ( ! $class->hasAssociation($field)) {
1061
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
1062
        }
1063
1064 107
        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 369
    public function PathExpression($expectedTypes)
1078
    {
1079 369
        $identVariable = $this->IdentificationVariable();
1080 369
        $field = null;
1081
1082 369
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1083 368
            $this->match(Lexer::T_DOT);
1084 368
            $this->match(Lexer::T_IDENTIFIER);
1085
1086 368
            $field = $this->lexer->token['value'];
1087
1088 368
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1089 1
                $this->match(Lexer::T_DOT);
1090 1
                $this->match(Lexer::T_IDENTIFIER);
1091 1
                $field .= '.'.$this->lexer->token['value'];
1092
            }
1093
        }
1094
1095
        // Creating AST node
1096 369
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1097
1098
        // Defer PathExpression validation if requested to be deferred
1099 369
        $this->deferredPathExpressions[] = [
1100 369
            'expression'   => $pathExpr,
1101 369
            'nestingLevel' => $this->nestingLevel,
1102 369
            'token'        => $this->lexer->token,
1103
        ];
1104
1105 369
        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 288
    public function SingleValuedPathExpression()
1127
    {
1128 288
        return $this->PathExpression(
1129 288
            AST\PathExpression::TYPE_STATE_FIELD |
1130 288
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1131
        );
1132
    }
1133
1134
    /**
1135
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1136
     *
1137
     * @return \Doctrine\ORM\Query\AST\PathExpression
1138
     */
1139 140
    public function StateFieldPathExpression()
1140
    {
1141 140
        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 8
    public function SingleValuedAssociationPathExpression()
1150
    {
1151 8
        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 20
    public function CollectionValuedPathExpression()
1160
    {
1161 20
        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 453
    public function SelectClause()
1170
    {
1171 453
        $isDistinct = false;
1172 453
        $this->match(Lexer::T_SELECT);
1173
1174
        // Check for DISTINCT
1175 453
        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 453
        $selectExpressions = [];
1183 453
        $selectExpressions[] = $this->SelectExpression();
1184
1185 445
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1186 131
            $this->match(Lexer::T_COMMA);
1187
1188 131
            $selectExpressions[] = $this->SelectExpression();
1189
        }
1190
1191 444
        return new AST\SelectClause($selectExpressions, $isDistinct);
1192
    }
1193
1194
    /**
1195
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1196
     *
1197
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1198
     */
1199 43
    public function SimpleSelectClause()
1200
    {
1201 43
        $isDistinct = false;
1202 43
        $this->match(Lexer::T_SELECT);
1203
1204 43
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1205
            $this->match(Lexer::T_DISTINCT);
1206
1207
            $isDistinct = true;
1208
        }
1209
1210 43
        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 26
    public function UpdateClause()
1219
    {
1220 26
        $this->match(Lexer::T_UPDATE);
1221
1222 26
        $token = $this->lexer->lookahead;
1223 26
        $abstractSchemaName = $this->AbstractSchemaName();
1224
1225 26
        $this->validateAbstractSchemaName($abstractSchemaName);
1226
1227 26
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1228
            $this->match(Lexer::T_AS);
1229
        }
1230
1231 26
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1232
1233 26
        $class = $this->em->getClassMetadata($abstractSchemaName);
1234
1235
        // Building queryComponent
1236
        $queryComponent = [
1237 26
            'metadata'     => $class,
1238
            'parent'       => null,
1239
            'relation'     => null,
1240
            'map'          => null,
1241 26
            'nestingLevel' => $this->nestingLevel,
1242 26
            'token'        => $token,
1243
        ];
1244
1245 26
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1246
1247 26
        $this->match(Lexer::T_SET);
1248
1249 26
        $updateItems = [];
1250 26
        $updateItems[] = $this->UpdateItem();
1251
1252 26
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1253 2
            $this->match(Lexer::T_COMMA);
1254
1255 2
            $updateItems[] = $this->UpdateItem();
1256
        }
1257
1258 26
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1259 26
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1260
1261 26
        return $updateClause;
1262
    }
1263
1264
    /**
1265
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1266
     *
1267
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1268
     */
1269 38
    public function DeleteClause()
1270
    {
1271 38
        $this->match(Lexer::T_DELETE);
1272
1273 38
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1274 7
            $this->match(Lexer::T_FROM);
1275
        }
1276
1277 38
        $token = $this->lexer->lookahead;
1278 38
        $abstractSchemaName = $this->AbstractSchemaName();
1279
1280 38
        $this->validateAbstractSchemaName($abstractSchemaName);
1281
1282 38
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1283
1284 38
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1285
            $this->match(Lexer::T_AS);
1286
        }
1287
1288 38
        $aliasIdentificationVariable = $this->lexer->isNextToken(Lexer::T_IDENTIFIER)
1289 36
            ? $this->AliasIdentificationVariable()
1290 38
            : 'alias_should_have_been_set';
1291
1292 38
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1293 38
        $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1294
1295
        // Building queryComponent
1296
        $queryComponent = [
1297 38
            'metadata'     => $class,
1298
            'parent'       => null,
1299
            'relation'     => null,
1300
            'map'          => null,
1301 38
            'nestingLevel' => $this->nestingLevel,
1302 38
            'token'        => $token,
1303
        ];
1304
1305 38
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1306
1307 38
        return $deleteClause;
1308
    }
1309
1310
    /**
1311
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1312
     *
1313
     * @return \Doctrine\ORM\Query\AST\FromClause
1314
     */
1315 444
    public function FromClause()
1316
    {
1317 444
        $this->match(Lexer::T_FROM);
1318
1319 439
        $identificationVariableDeclarations = [];
1320 439
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1321
1322 419
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1323 6
            $this->match(Lexer::T_COMMA);
1324
1325 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1326
        }
1327
1328 419
        return new AST\FromClause($identificationVariableDeclarations);
1329
    }
1330
1331
    /**
1332
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1333
     *
1334
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1335
     */
1336 43
    public function SubselectFromClause()
1337
    {
1338 43
        $this->match(Lexer::T_FROM);
1339
1340 43
        $identificationVariables = [];
1341 43
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1342
1343 42
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1344
            $this->match(Lexer::T_COMMA);
1345
1346
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1347
        }
1348
1349 42
        return new AST\SubselectFromClause($identificationVariables);
1350
    }
1351
1352
    /**
1353
     * WhereClause ::= "WHERE" ConditionalExpression
1354
     *
1355
     * @return \Doctrine\ORM\Query\AST\WhereClause
1356
     */
1357 237
    public function WhereClause()
1358
    {
1359 237
        $this->match(Lexer::T_WHERE);
1360
1361 237
        return new AST\WhereClause($this->ConditionalExpression());
1362
    }
1363
1364
    /**
1365
     * HavingClause ::= "HAVING" ConditionalExpression
1366
     *
1367
     * @return \Doctrine\ORM\Query\AST\HavingClause
1368
     */
1369 18
    public function HavingClause()
1370
    {
1371 18
        $this->match(Lexer::T_HAVING);
1372
1373 18
        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 25
    public function GroupByClause()
1382
    {
1383 25
        $this->match(Lexer::T_GROUP);
1384 25
        $this->match(Lexer::T_BY);
1385
1386 25
        $groupByItems = [$this->GroupByItem()];
1387
1388 24
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1389 2
            $this->match(Lexer::T_COMMA);
1390
1391 2
            $groupByItems[] = $this->GroupByItem();
1392
        }
1393
1394 24
        return new AST\GroupByClause($groupByItems);
1395
    }
1396
1397
    /**
1398
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1399
     *
1400
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1401
     */
1402 52
    public function OrderByClause()
1403
    {
1404 52
        $this->match(Lexer::T_ORDER);
1405 52
        $this->match(Lexer::T_BY);
1406
1407 52
        $orderByItems = [];
1408 52
        $orderByItems[] = $this->OrderByItem();
1409
1410 52
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1411 6
            $this->match(Lexer::T_COMMA);
1412
1413 6
            $orderByItems[] = $this->OrderByItem();
1414
        }
1415
1416 52
        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 43
    public function Subselect()
1425
    {
1426
        // Increase query nesting level
1427 43
        $this->nestingLevel++;
1428
1429 43
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1430
1431 42
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1432 42
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1433 42
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1434 42
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1435
1436
        // Decrease query nesting level
1437 42
        $this->nestingLevel--;
1438
1439 42
        return $subselect;
1440
    }
1441
1442
    /**
1443
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1444
     *
1445
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1446
     */
1447 26
    public function UpdateItem()
1448
    {
1449 26
        $pathExpr = $this->SingleValuedPathExpression();
1450
1451 26
        $this->match(Lexer::T_EQUALS);
1452
1453 26
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1454
1455 26
        return $updateItem;
1456
    }
1457
1458
    /**
1459
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1460
     *
1461
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1462
     */
1463 25
    public function GroupByItem()
1464
    {
1465
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1466 25
        $glimpse = $this->lexer->glimpse();
1467
1468 25
        if ($glimpse !== null && $glimpse['type'] === Lexer::T_DOT) {
1469 11
            return $this->SingleValuedPathExpression();
1470
        }
1471
1472
        // Still need to decide between IdentificationVariable or ResultVariable
1473 14
        $lookaheadValue = $this->lexer->lookahead['value'];
1474
1475 14
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1476 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1477
        }
1478
1479 13
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1480 11
            ? $this->IdentificationVariable()
1481 13
            : $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 52
    public function OrderByItem()
1493
    {
1494 52
        $this->lexer->peek(); // lookahead => '.'
1495 52
        $this->lexer->peek(); // lookahead => token after '.'
1496
1497 52
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1498
1499 52
        $this->lexer->resetPeek();
1500
1501 52
        $glimpse = $this->lexer->glimpse();
1502
1503
        switch (true) {
1504 52
            case ($this->isFunction()):
1505 2
                $expr = $this->FunctionDeclaration();
1506 2
                break;
1507
1508 50
            case ($this->isMathOperator($peek)):
1509 7
                $expr = $this->SimpleArithmeticExpression();
1510 7
                break;
1511
1512 44
            case $glimpse !== null && $glimpse['type'] === Lexer::T_DOT:
1513 33
                $expr = $this->SingleValuedPathExpression();
1514 33
                break;
1515
1516 14
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1517 2
                $expr = $this->ScalarExpression();
1518 2
                break;
1519
1520
            default:
1521 12
                $expr = $this->ResultVariable();
1522 12
                break;
1523
        }
1524
1525 52
        $type = 'ASC';
1526 52
        $item = new AST\OrderByItem($expr);
1527
1528
        switch (true) {
1529 52
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1530 25
                $this->match(Lexer::T_DESC);
1531 25
                $type = 'DESC';
1532 25
                break;
1533
1534 31
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1535 10
                $this->match(Lexer::T_ASC);
1536 10
                break;
1537
1538
            default:
1539
                // Do nothing
1540
        }
1541
1542 52
        $item->type = $type;
1543
1544 52
        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 26
    public function NewValue()
1561
    {
1562 26
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1563 1
            $this->match(Lexer::T_NULL);
1564
1565 1
            return null;
1566
        }
1567
1568 25
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1569 14
            $this->match(Lexer::T_INPUT_PARAMETER);
1570
1571 14
            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 11
        return $this->ArithmeticExpression();
1575
    }
1576
1577
    /**
1578
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1579
     *
1580
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1581
     */
1582 441
    public function IdentificationVariableDeclaration()
1583
    {
1584 441
        $joins                    = [];
1585 441
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1586 424
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1587 2
            ? $this->IndexBy()
1588 424
            : null;
1589
1590 424
        $rangeVariableDeclaration->isRoot = true;
1591
1592
        while (
1593 424
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1594 424
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1595 424
            $this->lexer->isNextToken(Lexer::T_JOIN)
1596
        ) {
1597 127
            $joins[] = $this->Join();
1598
        }
1599
1600 421
        return new AST\IdentificationVariableDeclaration(
1601 421
            $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 43
    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 43
        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 127
    public function Join()
1672
    {
1673
        // Check Join type
1674 127
        $joinType = AST\Join::JOIN_TYPE_INNER;
1675
1676
        switch (true) {
1677 127
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1678 36
                $this->match(Lexer::T_LEFT);
1679
1680 36
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1681
1682
                // Possible LEFT OUTER join
1683 36
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1684
                    $this->match(Lexer::T_OUTER);
1685
1686
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1687
                }
1688 36
                break;
1689
1690 95
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1691 10
                $this->match(Lexer::T_INNER);
1692 10
                break;
1693
1694
            default:
1695
                // Do nothing
1696
        }
1697
1698 127
        $this->match(Lexer::T_JOIN);
1699
1700 127
        $next            = $this->lexer->glimpse();
1701 127
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1702 124
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1703 124
        $join            = new AST\Join($joinType, $joinDeclaration);
1704
1705
        // Describe non-root join declaration
1706 124
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1707 20
            $joinDeclaration->isRoot = false;
1708
        }
1709
1710
        // Check for ad-hoc Join conditions
1711 124
        if ($adhocConditions) {
1712 21
            $this->match(Lexer::T_WITH);
1713
1714 21
            $join->conditionalExpression = $this->ConditionalExpression();
1715
        }
1716
1717 124
        return $join;
1718
    }
1719
1720
    /**
1721
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1722
     *
1723
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1724
     *
1725
     * @throws QueryException
1726
     */
1727 441
    public function RangeVariableDeclaration()
1728
    {
1729 441
        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 440
        $abstractSchemaName = $this->AbstractSchemaName();
1734
1735 439
        $this->validateAbstractSchemaName($abstractSchemaName);
1736
1737 424
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1738
            $this->match(Lexer::T_AS);
1739
        }
1740
1741 424
        $token = $this->lexer->lookahead;
1742 424
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1743 424
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1744
1745
        // Building queryComponent
1746
        $queryComponent = [
1747 424
            'metadata'     => $classMetadata,
1748
            'parent'       => null,
1749
            'relation'     => null,
1750
            'map'          => null,
1751 424
            'nestingLevel' => $this->nestingLevel,
1752 424
            'token'        => $token
1753
        ];
1754
1755 424
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1756
1757 424
        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 107
    public function JoinAssociationDeclaration()
1766
    {
1767 107
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1768
1769 107
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1770
            $this->match(Lexer::T_AS);
1771
        }
1772
1773 107
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1774 105
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1775
1776 105
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1777 105
        $field                  = $joinAssociationPathExpression->associationField;
1778
1779 105
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1780 105
        $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1781
1782
        // Building queryComponent
1783
        $joinQueryComponent = [
1784 105
            'metadata'     => $targetClass,
1785 105
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1786 105
            'relation'     => $class->getAssociationMapping($field),
1787
            'map'          => null,
1788 105
            'nestingLevel' => $this->nestingLevel,
1789 105
            'token'        => $this->lexer->lookahead
1790
        ];
1791
1792 105
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1793
1794 105
        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 7
    public function PartialObjectExpression()
1804
    {
1805 7
        $this->match(Lexer::T_PARTIAL);
1806
1807 7
        $partialFieldSet = [];
1808
1809 7
        $identificationVariable = $this->IdentificationVariable();
1810
1811 7
        $this->match(Lexer::T_DOT);
1812 7
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1813 7
        $this->match(Lexer::T_IDENTIFIER);
1814
1815 7
        $field = $this->lexer->token['value'];
1816
1817
        // First field in partial expression might be embeddable property
1818 7
        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 7
        $partialFieldSet[] = $field;
1825
1826 7
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1827 5
            $this->match(Lexer::T_COMMA);
1828 5
            $this->match(Lexer::T_IDENTIFIER);
1829
1830 5
            $field = $this->lexer->token['value'];
1831
1832 5
            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 5
            $partialFieldSet[] = $field;
1839
        }
1840
1841 7
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1842
1843 7
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1844
1845
        // Defer PartialObjectExpression validation
1846 7
        $this->deferredPartialObjectExpressions[] = [
1847 7
            'expression'   => $partialObjectExpression,
1848 7
            'nestingLevel' => $this->nestingLevel,
1849 7
            'token'        => $this->lexer->token,
1850
        ];
1851
1852 7
        return $partialObjectExpression;
1853
    }
1854
1855
    /**
1856
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1857
     *
1858
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1859
     */
1860 7
    public function NewObjectExpression()
1861
    {
1862 7
        $this->match(Lexer::T_NEW);
1863
1864 7
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1865 7
        $token = $this->lexer->token;
1866
1867 7
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1868
1869 7
        $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 7
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1872 3
            $this->match(Lexer::T_COMMA);
1873
1874 3
            $args[] = $this->NewObjectArg();
1875
        }
1876
1877 7
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1878
1879 7
        $expression = new AST\NewObjectExpression($className, $args);
1880
1881
        // Defer NewObjectExpression validation
1882 7
        $this->deferredNewObjectExpressions[] = [
1883 7
            'token'        => $token,
1884 7
            'expression'   => $expression,
1885 7
            'nestingLevel' => $this->nestingLevel,
1886
        ];
1887
1888 7
        return $expression;
1889
    }
1890
1891
    /**
1892
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1893
     *
1894
     * @return mixed
1895
     */
1896 7
    public function NewObjectArg()
1897
    {
1898 7
        $token = $this->lexer->lookahead;
1899 7
        $peek  = $this->lexer->glimpse();
1900
1901 7
        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 7
        return $this->ScalarExpression();
1910
    }
1911
1912
    /**
1913
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1914
     *
1915
     * @return \Doctrine\ORM\Query\AST\IndexBy
1916
     */
1917 3
    public function IndexBy()
1918
    {
1919 3
        $this->match(Lexer::T_INDEX);
1920 3
        $this->match(Lexer::T_BY);
1921 3
        $pathExpr = $this->StateFieldPathExpression();
1922
1923
        // Add the INDEX BY info to the query component
1924 3
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1925
1926 3
        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 121
    public function ScalarExpression()
1937
    {
1938 121
        $lookahead = $this->lexer->lookahead['type'];
1939 121
        $peek      = $this->lexer->glimpse();
1940
1941
        switch (true) {
1942 121
            case ($lookahead === Lexer::T_INTEGER):
1943 118
            case ($lookahead === Lexer::T_FLOAT):
1944
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1945 118
            case ($lookahead === Lexer::T_MINUS):
1946 118
            case ($lookahead === Lexer::T_PLUS):
1947 16
                return $this->SimpleArithmeticExpression();
1948
1949 118
            case ($lookahead === Lexer::T_STRING):
1950 11
                return $this->StringPrimary();
1951
1952 116
            case ($lookahead === Lexer::T_TRUE):
1953 116
            case ($lookahead === Lexer::T_FALSE):
1954 2
                $this->match($lookahead);
1955
1956 2
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1957
1958 116
            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 116
            case ($lookahead === Lexer::T_CASE):
1968 112
            case ($lookahead === Lexer::T_COALESCE):
1969 112
            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 7
                return $this->CaseExpression();
1973
1974 112
            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 109
            case ($this->isFunction()):
1979 21
                $this->lexer->peek(); // "("
1980
1981
                switch (true) {
1982 21
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1983
                        // SUM(u.id) + COUNT(u.id)
1984 6
                        return $this->SimpleArithmeticExpression();
1985
1986
                    default:
1987
                        // IDENTITY(u)
1988 17
                        return $this->FunctionDeclaration();
1989
                }
1990
1991
                break;
1992
            // it is no function, so it must be a field path
1993 94
            case ($lookahead === Lexer::T_IDENTIFIER):
1994 94
                $this->lexer->peek(); // lookahead => '.'
1995 94
                $this->lexer->peek(); // lookahead => token after '.'
1996 94
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1997 94
                $this->lexer->resetPeek();
1998
1999 94
                if ($this->isMathOperator($peek)) {
2000 3
                    return $this->SimpleArithmeticExpression();
2001
                }
2002
2003 92
                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 18
    public function CaseExpression()
2023
    {
2024 18
        $lookahead = $this->lexer->lookahead['type'];
2025
2026
        switch ($lookahead) {
2027 18
            case Lexer::T_NULLIF:
2028 5
                return $this->NullIfExpression();
2029
2030 15
            case Lexer::T_COALESCE:
2031 2
                return $this->CoalesceExpression();
2032
2033 13
            case Lexer::T_CASE:
2034 13
                $this->lexer->resetPeek();
2035 13
                $peek = $this->lexer->peek();
2036
2037 13
                if ($peek['type'] === Lexer::T_WHEN) {
2038 8
                    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 8
    public function GeneralCaseExpression()
2101
    {
2102 8
        $this->match(Lexer::T_CASE);
2103
2104
        // Process WhenClause (1..N)
2105 8
        $whenClauses = [];
2106
2107
        do {
2108 8
            $whenClauses[] = $this->WhenClause();
2109 8
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2110
2111 8
        $this->match(Lexer::T_ELSE);
2112 8
        $scalarExpression = $this->ScalarExpression();
2113 8
        $this->match(Lexer::T_END);
2114
2115 8
        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 8
    public function WhenClause()
2149
    {
2150 8
        $this->match(Lexer::T_WHEN);
2151 8
        $conditionalExpression = $this->ConditionalExpression();
2152 8
        $this->match(Lexer::T_THEN);
2153
2154 8
        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 453
    public function SelectExpression()
2180
    {
2181 453
        $expression    = null;
2182 453
        $identVariable = null;
2183 453
        $peek          = $this->lexer->glimpse();
2184 453
        $lookaheadType = $this->lexer->lookahead['type'];
2185
2186
        switch (true) {
2187
            // ScalarExpression (u.name)
2188 453
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2189 81
                $expression = $this->ScalarExpression();
2190 81
                break;
2191
2192
            // IdentificationVariable (u)
2193 398
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2194 325
                $expression = $identVariable = $this->IdentificationVariable();
2195 325
                break;
2196
2197
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
2198 115
            case ($lookaheadType === Lexer::T_CASE):
2199 110
            case ($lookaheadType === Lexer::T_COALESCE):
2200 108
            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 106
            case ($this->isFunction()):
2206 56
                $this->lexer->peek(); // "("
2207
2208
                switch (true) {
2209 56
                    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 54
                        $expression = $this->FunctionDeclaration();
2217 54
                        break;
2218
                }
2219
2220 56
                break;
2221
2222
            // PartialObjectExpression (PARTIAL u.{id, name})
2223 51
            case ($lookaheadType === Lexer::T_PARTIAL):
2224 7
                $expression    = $this->PartialObjectExpression();
2225 7
                $identVariable = $expression->identificationVariable;
2226 7
                break;
2227
2228
            // Subselect
2229 44
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2230 19
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2231 19
                $expression = $this->Subselect();
2232 19
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2233 19
                break;
2234
2235
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2236 25
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2237 22
            case ($lookaheadType === Lexer::T_INTEGER):
2238 20
            case ($lookaheadType === Lexer::T_STRING):
2239 11
            case ($lookaheadType === Lexer::T_FLOAT):
2240
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2241 11
            case ($lookaheadType === Lexer::T_MINUS):
2242 11
            case ($lookaheadType === Lexer::T_PLUS):
2243 15
                $expression = $this->SimpleArithmeticExpression();
2244 15
                break;
2245
2246
            // NewObjectExpression (New ClassName(id, name))
2247 10
            case ($lookaheadType === Lexer::T_NEW):
2248 7
                $expression = $this->NewObjectExpression();
2249 7
                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 450
        $mustHaveAliasResultVariable = false;
2260
2261 450
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2262 71
            $this->match(Lexer::T_AS);
2263
2264 71
            $mustHaveAliasResultVariable = true;
2265
        }
2266
2267 450
        $hiddenAliasResultVariable = false;
2268
2269 450
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2270 7
            $this->match(Lexer::T_HIDDEN);
2271
2272 7
            $hiddenAliasResultVariable = true;
2273
        }
2274
2275 450
        $aliasResultVariable = null;
2276
2277 450
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2278 80
            $token = $this->lexer->lookahead;
2279 80
            $aliasResultVariable = $this->AliasResultVariable();
2280
2281
            // Include AliasResultVariable in query components.
2282 75
            $this->queryComponents[$aliasResultVariable] = [
2283 75
                'resultVariable' => $expression,
2284 75
                'nestingLevel'   => $this->nestingLevel,
2285 75
                'token'          => $token,
2286
            ];
2287
        }
2288
2289
        // AST
2290
2291 445
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2292
2293 445
        if ($identVariable) {
2294 331
            $this->identVariableExpressions[$identVariable] = $expr;
2295
        }
2296
2297 445
        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 43
    public function SimpleSelectExpression()
2309
    {
2310 43
        $peek = $this->lexer->glimpse();
2311
2312 43
        switch ($this->lexer->lookahead['type']) {
2313 43
            case Lexer::T_IDENTIFIER:
2314
                switch (true) {
2315 17
                    case ($peek['type'] === Lexer::T_DOT):
2316 14
                        $expression = $this->StateFieldPathExpression();
2317
2318 14
                        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 27
            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 24
        $this->lexer->peek();
2362
2363 24
        $expression = $this->ScalarExpression();
2364 24
        $expr       = new AST\SimpleSelectExpression($expression);
2365
2366 24
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2367 1
            $this->match(Lexer::T_AS);
2368
        }
2369
2370 24
        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 24
        return $expr;
2384
    }
2385
2386
    /**
2387
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2388
     *
2389
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2390
     */
2391 273
    public function ConditionalExpression()
2392
    {
2393 273
        $conditionalTerms = [];
2394 273
        $conditionalTerms[] = $this->ConditionalTerm();
2395
2396 270
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2397 13
            $this->match(Lexer::T_OR);
2398
2399 13
            $conditionalTerms[] = $this->ConditionalTerm();
2400
        }
2401
2402
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2403
        // if only one AST\ConditionalTerm is defined
2404 270
        if (count($conditionalTerms) == 1) {
2405 264
            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 13
        return new AST\ConditionalExpression($conditionalTerms);
2409
    }
2410
2411
    /**
2412
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2413
     *
2414
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2415
     */
2416 273
    public function ConditionalTerm()
2417
    {
2418 273
        $conditionalFactors = [];
2419 273
        $conditionalFactors[] = $this->ConditionalFactor();
2420
2421 270
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2422 23
            $this->match(Lexer::T_AND);
2423
2424 23
            $conditionalFactors[] = $this->ConditionalFactor();
2425
        }
2426
2427
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2428
        // if only one AST\ConditionalFactor is defined
2429 270
        if (count($conditionalFactors) == 1) {
2430 259
            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 23
        return new AST\ConditionalTerm($conditionalFactors);
2434
    }
2435
2436
    /**
2437
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2438
     *
2439
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2440
     */
2441 273
    public function ConditionalFactor()
2442
    {
2443 273
        $not = false;
2444
2445 273
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2446 6
            $this->match(Lexer::T_NOT);
2447
2448 6
            $not = true;
2449
        }
2450
2451 273
        $conditionalPrimary = $this->ConditionalPrimary();
2452
2453
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2454
        // if only one AST\ConditionalPrimary is defined
2455 270
        if ( ! $not) {
2456 268
            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 273
    public function ConditionalPrimary()
2471
    {
2472 273
        $condPrimary = new AST\ConditionalPrimary;
2473
2474 273
        if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2475 265
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2476
2477 262
            return $condPrimary;
2478
        }
2479
2480
        // Peek beyond the matching closing parenthesis ')'
2481 22
        $peek = $this->peekBeyondClosingParenthesis();
2482
2483 22
        if ($peek !== null && (
2484 19
            in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!=']) ||
2485 15
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
2486 22
            $this->isMathOperator($peek)
2487
        )) {
2488 14
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2489
2490 14
            return $condPrimary;
2491
        }
2492
2493 18
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2494 18
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2495 18
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2496
2497 18
        return $condPrimary;
2498
    }
2499
2500
    /**
2501
     * SimpleConditionalExpression ::=
2502
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2503
     *      InExpression | NullComparisonExpression | ExistsExpression |
2504
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2505
     *      InstanceOfExpression
2506
     */
2507 273
    public function SimpleConditionalExpression()
2508
    {
2509 273
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2510 7
            return $this->ExistsExpression();
2511
        }
2512
2513 273
        $token      = $this->lexer->lookahead;
2514 273
        $peek       = $this->lexer->glimpse();
2515 273
        $lookahead  = $token;
2516
2517 273
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2518
            $token = $this->lexer->glimpse();
2519
        }
2520
2521 273
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2522
            // Peek beyond the matching closing parenthesis.
2523 252
            $beyond = $this->lexer->peek();
2524
2525 252
            switch ($peek['value']) {
2526 252
                case '(':
2527
                    // Peeks beyond the matched closing parenthesis.
2528 29
                    $token = $this->peekBeyondClosingParenthesis(false);
2529
2530 29
                    if ($token['type'] === Lexer::T_NOT) {
2531 3
                        $token = $this->lexer->peek();
2532
                    }
2533
2534 29
                    if ($token['type'] === Lexer::T_IS) {
2535 2
                        $lookahead = $this->lexer->peek();
2536
                    }
2537 29
                    break;
2538
2539
                default:
2540
                    // Peek beyond the PathExpression or InputParameter.
2541 229
                    $token = $beyond;
2542
2543 229
                    while ($token['value'] === '.') {
2544 199
                        $this->lexer->peek();
2545
2546 199
                        $token = $this->lexer->peek();
2547
                    }
2548
2549
                    // Also peek beyond a NOT if there is one.
2550 229
                    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 229
                    $lookahead = $this->lexer->peek();
2556
            }
2557
2558
            // Also peek beyond a NOT if there is one.
2559 252
            if ($lookahead['type'] === Lexer::T_NOT) {
2560 5
                $lookahead = $this->lexer->peek();
2561
            }
2562
2563 252
            $this->lexer->resetPeek();
2564
        }
2565
2566 273
        if ($token['type'] === Lexer::T_BETWEEN) {
2567 8
            return $this->BetweenExpression();
2568
        }
2569
2570 267
        if ($token['type'] === Lexer::T_LIKE) {
2571 14
            return $this->LikeExpression();
2572
        }
2573
2574 254
        if ($token['type'] === Lexer::T_IN) {
2575 26
            return $this->InExpression();
2576
        }
2577
2578 234
        if ($token['type'] === Lexer::T_INSTANCE) {
2579 10
            return $this->InstanceOfExpression();
2580
        }
2581
2582 224
        if ($token['type'] === Lexer::T_MEMBER) {
2583 7
            return $this->CollectionMemberExpression();
2584
        }
2585
2586 217
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2587 12
            return $this->NullComparisonExpression();
2588
        }
2589
2590 208
        if ($token['type'] === Lexer::T_IS  && $lookahead['type'] === Lexer::T_EMPTY) {
2591 3
            return $this->EmptyCollectionComparisonExpression();
2592
        }
2593
2594 205
        return $this->ComparisonExpression();
2595
    }
2596
2597
    /**
2598
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2599
     *
2600
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2601
     */
2602 3
    public function EmptyCollectionComparisonExpression()
2603
    {
2604 3
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2605 3
            $this->CollectionValuedPathExpression()
2606
        );
2607 3
        $this->match(Lexer::T_IS);
2608
2609 3
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2610 1
            $this->match(Lexer::T_NOT);
2611 1
            $emptyCollectionCompExpr->not = true;
2612
        }
2613
2614 3
        $this->match(Lexer::T_EMPTY);
2615
2616 3
        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 7
    public function CollectionMemberExpression()
2628
    {
2629 7
        $not        = false;
2630 7
        $entityExpr = $this->EntityExpression();
2631
2632 7
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2633
            $this->match(Lexer::T_NOT);
2634
2635
            $not = true;
2636
        }
2637
2638 7
        $this->match(Lexer::T_MEMBER);
2639
2640 7
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2641 7
            $this->match(Lexer::T_OF);
2642
        }
2643
2644 7
        $collMemberExpr = new AST\CollectionMemberExpression(
2645 7
            $entityExpr, $this->CollectionValuedPathExpression()
2646
        );
2647 7
        $collMemberExpr->not = $not;
2648
2649 7
        return $collMemberExpr;
2650
    }
2651
2652
    /**
2653
     * Literal ::= string | char | integer | float | boolean
2654
     *
2655
     * @return \Doctrine\ORM\Query\AST\Literal
2656
     */
2657 138
    public function Literal()
2658
    {
2659 138
        switch ($this->lexer->lookahead['type']) {
2660 138
            case Lexer::T_STRING:
2661 37
                $this->match(Lexer::T_STRING);
2662
2663 37
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2664 108
            case Lexer::T_INTEGER:
2665 7
            case Lexer::T_FLOAT:
2666 101
                $this->match(
2667 101
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2668
                );
2669
2670 101
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2671 7
            case Lexer::T_TRUE:
2672 3
            case Lexer::T_FALSE:
2673 7
                $this->match(
2674 7
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2675
                );
2676
2677 7
                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 18
    public function InParameter()
2689
    {
2690 18
        if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2691 8
            return $this->InputParameter();
2692
        }
2693
2694 10
        return $this->Literal();
2695
    }
2696
2697
    /**
2698
     * InputParameter ::= PositionalParameter | NamedParameter
2699
     *
2700
     * @return \Doctrine\ORM\Query\AST\InputParameter
2701
     */
2702 96
    public function InputParameter()
2703
    {
2704 96
        $this->match(Lexer::T_INPUT_PARAMETER);
2705
2706 96
        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 233
    public function ArithmeticExpression()
2715
    {
2716 233
        $expr = new AST\ArithmeticExpression;
2717
2718 233
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2719 18
            $peek = $this->lexer->glimpse();
2720
2721 18
            if ($peek['type'] === Lexer::T_SELECT) {
2722 6
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2723 6
                $expr->subselect = $this->Subselect();
2724 6
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2725
2726 6
                return $expr;
2727
            }
2728
        }
2729
2730 233
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2731
2732 233
        return $expr;
2733
    }
2734
2735
    /**
2736
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2737
     *
2738
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2739
     */
2740 295
    public function SimpleArithmeticExpression()
2741
    {
2742 295
        $terms = [];
2743 295
        $terms[] = $this->ArithmeticTerm();
2744
2745 295
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2746 17
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2747
2748 17
            $terms[] = $this->lexer->token['value'];
2749 17
            $terms[] = $this->ArithmeticTerm();
2750
        }
2751
2752
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2753
        // if only one AST\ArithmeticTerm is defined
2754 295
        if (count($terms) == 1) {
2755 293
            return $terms[0];
2756
        }
2757
2758 17
        return new AST\SimpleArithmeticExpression($terms);
2759
    }
2760
2761
    /**
2762
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2763
     *
2764
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2765
     */
2766 295
    public function ArithmeticTerm()
2767
    {
2768 295
        $factors = [];
2769 295
        $factors[] = $this->ArithmeticFactor();
2770
2771 295
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2772 30
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2773
2774 30
            $factors[] = $this->lexer->token['value'];
2775 30
            $factors[] = $this->ArithmeticFactor();
2776
        }
2777
2778
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2779
        // if only one AST\ArithmeticFactor is defined
2780 295
        if (count($factors) == 1) {
2781 289
            return $factors[0];
2782
        }
2783
2784 30
        return new AST\ArithmeticTerm($factors);
2785
    }
2786
2787
    /**
2788
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2789
     *
2790
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2791
     */
2792 295
    public function ArithmeticFactor()
2793
    {
2794 295
        $sign = null;
2795
2796 295
        if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2797 2
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2798 2
            $sign = $isPlus;
2799
        }
2800
2801 295
        $primary = $this->ArithmeticPrimary();
2802
2803
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2804
        // if only one AST\ArithmeticPrimary is defined
2805 295
        if ($sign === null) {
2806 294
            return $primary;
2807
        }
2808
2809 2
        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 295
    public function ArithmeticPrimary()
2819
    {
2820 295
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2821 22
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2822
2823 22
            $expr = $this->SimpleArithmeticExpression();
2824
2825 22
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2826
2827 22
            return new AST\ParenthesisExpression($expr);
2828
        }
2829
2830 295
        switch ($this->lexer->lookahead['type']) {
2831 295
            case Lexer::T_COALESCE:
2832 295
            case Lexer::T_NULLIF:
2833 295
            case Lexer::T_CASE:
2834 4
                return $this->CaseExpression();
2835
2836 295
            case Lexer::T_IDENTIFIER:
2837 270
                $peek = $this->lexer->glimpse();
2838
2839 270
                if ($peek !== null && $peek['value'] === '(') {
2840 23
                    return $this->FunctionDeclaration();
2841
                }
2842
2843 254
                if ($peek !== null && $peek['value'] === '.') {
0 ignored issues
show
introduced by
The condition $peek !== null is always false.
Loading history...
2844 251
                    return $this->SingleValuedPathExpression();
2845
                }
2846
2847 31
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2848 10
                    return $this->ResultVariable();
2849
                }
2850
2851 23
                return $this->StateFieldPathExpression();
2852
2853 206
            case Lexer::T_INPUT_PARAMETER:
2854 81
                return $this->InputParameter();
2855
2856
            default:
2857 133
                $peek = $this->lexer->glimpse();
2858
2859 133
                if ($peek !== null && $peek['value'] === '(') {
2860 14
                    return $this->FunctionDeclaration();
2861
                }
2862
2863 130
                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 37
    public function StringPrimary()
2899
    {
2900 37
        $lookaheadType = $this->lexer->lookahead['type'];
2901
2902
        switch ($lookaheadType) {
2903 37
            case Lexer::T_IDENTIFIER:
2904 24
                $peek = $this->lexer->glimpse();
2905
2906 24
                if ($peek['value'] == '.') {
2907 24
                    return $this->StateFieldPathExpression();
2908
                }
2909
2910 5
                if ($peek['value'] == '(') {
2911
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2912 5
                    return $this->FunctionDeclaration();
2913
                }
2914
2915
                $this->syntaxError("'.' or '('");
2916
                break;
2917
2918 25
            case Lexer::T_STRING:
2919 24
                $this->match(Lexer::T_STRING);
2920
2921 24
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2922
2923 3
            case Lexer::T_INPUT_PARAMETER:
2924 2
                return $this->InputParameter();
2925
2926 1
            case Lexer::T_CASE:
2927 1
            case Lexer::T_COALESCE:
2928 1
            case Lexer::T_NULLIF:
2929
                return $this->CaseExpression();
2930
            default:
2931 1
                if ($this->isAggregateFunction($lookaheadType)) {
2932 1
                    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 7
    public function EntityExpression()
2948
    {
2949 7
        $glimpse = $this->lexer->glimpse();
2950
2951 7
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2952 1
            return $this->SingleValuedAssociationPathExpression();
2953
        }
2954
2955 6
        return $this->SimpleEntityExpression();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->SimpleEntityExpression() returns the type Doctrine\ORM\Query\AST\InputParameter which is incompatible with the documented return type Doctrine\ORM\Query\AST\PathExpression.
Loading history...
2956
    }
2957
2958
    /**
2959
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2960
     *
2961
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2962
     */
2963 6
    public function SimpleEntityExpression()
2964
    {
2965 6
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2966 5
            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 68
    public function AggregateExpression()
2979
    {
2980 68
        $lookaheadType = $this->lexer->lookahead['type'];
2981 68
        $isDistinct = false;
2982
2983 68
        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 68
        $this->match($lookaheadType);
2988 68
        $functionName = $this->lexer->token['value'];
2989 68
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2990
2991 68
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2992 2
            $this->match(Lexer::T_DISTINCT);
2993 2
            $isDistinct = true;
2994
        }
2995
2996 68
        $pathExp = $this->SimpleArithmeticExpression();
2997
2998 68
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2999
3000 68
        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 205
    public function ComparisonExpression()
3060
    {
3061 205
        $this->lexer->glimpse();
3062
3063 205
        $leftExpr  = $this->ArithmeticExpression();
3064 205
        $operator  = $this->ComparisonOperator();
3065 205
        $rightExpr = ($this->isNextAllAnySome())
3066 3
            ? $this->QuantifiedExpression()
3067 205
            : $this->ArithmeticExpression();
3068
3069 203
        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 26
    public function InExpression()
3078
    {
3079 26
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3080
3081 26
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3082 6
            $this->match(Lexer::T_NOT);
3083 6
            $inExpression->not = true;
3084
        }
3085
3086 26
        $this->match(Lexer::T_IN);
3087 26
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3088
3089 26
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3090 8
            $inExpression->subselect = $this->Subselect();
3091
        } else {
3092 18
            $literals = [];
3093 18
            $literals[] = $this->InParameter();
3094
3095 18
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3096 14
                $this->match(Lexer::T_COMMA);
3097 14
                $literals[] = $this->InParameter();
3098
            }
3099
3100 18
            $inExpression->literals = $literals;
3101
        }
3102
3103 25
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3104
3105 25
        return $inExpression;
3106
    }
3107
3108
    /**
3109
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3110
     *
3111
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3112
     */
3113 10
    public function InstanceOfExpression()
3114
    {
3115 10
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3116
3117 10
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3118 1
            $this->match(Lexer::T_NOT);
3119 1
            $instanceOfExpression->not = true;
3120
        }
3121
3122 10
        $this->match(Lexer::T_INSTANCE);
3123 10
        $this->match(Lexer::T_OF);
3124
3125 10
        $exprValues = [];
3126
3127 10
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
3128 1
            $this->match(Lexer::T_OPEN_PARENTHESIS);
3129
3130 1
            $exprValues[] = $this->InstanceOfParameter();
3131
3132 1
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3133 1
                $this->match(Lexer::T_COMMA);
3134
3135 1
                $exprValues[] = $this->InstanceOfParameter();
3136
            }
3137
3138 1
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
3139
3140 1
            $instanceOfExpression->value = $exprValues;
3141
3142 1
            return $instanceOfExpression;
3143
        }
3144
3145 9
        $exprValues[] = $this->InstanceOfParameter();
3146
3147 9
        $instanceOfExpression->value = $exprValues;
3148
3149 9
        return $instanceOfExpression;
3150
    }
3151
3152
    /**
3153
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3154
     *
3155
     * @return mixed
3156
     */
3157 10
    public function InstanceOfParameter()
3158
    {
3159 10
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3160 3
            $this->match(Lexer::T_INPUT_PARAMETER);
3161
3162 3
            return new AST\InputParameter($this->lexer->token['value']);
3163
        }
3164
3165 7
        $abstractSchemaName = $this->AbstractSchemaName();
3166
3167 7
        $this->validateAbstractSchemaName($abstractSchemaName);
3168
3169 7
        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 12
    public function NullComparisonExpression()
3217
    {
3218
        switch (true) {
3219 12
            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 12
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3226 1
                $expr = $this->NullIfExpression();
3227 1
                break;
3228
3229 12
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3230 1
                $expr = $this->CoalesceExpression();
3231 1
                break;
3232
3233 12
            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 11
                $glimpse = $this->lexer->glimpse();
3240
3241 11
                if ($glimpse['type'] === Lexer::T_DOT) {
3242 8
                    $expr = $this->SingleValuedPathExpression();
3243
3244
                    // Leave switch statement
3245 8
                    break;
3246
                }
3247
3248 3
                $lookaheadValue = $this->lexer->lookahead['value'];
3249
3250
                // Validate existing component
3251 3
                if ( ! isset($this->queryComponents[$lookaheadValue])) {
3252
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3253
                }
3254
3255
                // Validate SingleValuedPathExpression (ie.: "product")
3256 3
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3257
                    $expr = $this->SingleValuedPathExpression();
3258
                    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 12
        $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 12
        $this->match(Lexer::T_IS);
3273
3274 12
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3275 4
            $this->match(Lexer::T_NOT);
3276
3277 4
            $nullCompExpr->not = true;
3278
        }
3279
3280 12
        $this->match(Lexer::T_NULL);
3281
3282 12
        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 205
    public function ComparisonOperator()
3316
    {
3317 205
        switch ($this->lexer->lookahead['value']) {
3318 205
            case '=':
3319 157
                $this->match(Lexer::T_EQUALS);
3320
3321 157
                return '=';
3322
3323 59
            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 50
            case '>':
3338 44
                $this->match(Lexer::T_GREATER_THAN);
3339 44
                $operator = '>';
3340
3341 44
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3342 6
                    $this->match(Lexer::T_EQUALS);
3343 6
                    $operator .= '=';
3344
                }
3345
3346 44
                return $operator;
3347
3348 6
            case '!':
3349 6
                $this->match(Lexer::T_NEGATE);
3350 6
                $this->match(Lexer::T_EQUALS);
3351
3352 6
                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 103
    public function FunctionDeclaration()
3365
    {
3366 103
        $token = $this->lexer->lookahead;
3367 103
        $funcName = strtolower($token['value']);
3368
3369 103
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3370
3371
        // Check for custom functions functions first!
3372
        switch (true) {
3373 103
            case $customFunctionDeclaration !== null:
3374 2
                return $customFunctionDeclaration;
3375
3376 101
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3377 21
                return $this->FunctionsReturningStrings();
3378
3379 83
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3380 80
                return $this->FunctionsReturningNumerics();
3381
3382 3
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3383 3
                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 103
    private function CustomFunctionDeclaration()
3396
    {
3397 103
        $token = $this->lexer->lookahead;
3398 103
        $funcName = strtolower($token['value']);
3399
3400
        // Check for custom functions afterwards
3401 103
        $config = $this->em->getConfiguration();
3402
3403
        switch (true) {
3404 103
            case ($config->getCustomStringFunction($funcName) !== null):
3405 1
                return $this->CustomFunctionsReturningStrings();
3406
3407 102
            case ($config->getCustomNumericFunction($funcName) !== null):
3408 1
                return $this->CustomFunctionsReturningNumerics();
3409
3410 101
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3411
                return $this->CustomFunctionsReturningDatetime();
3412
3413
            default:
3414 101
                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 80
    public function FunctionsReturningNumerics()
3433
    {
3434 80
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3435 80
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3436
3437 80
        $function = new $funcClass($funcNameLower);
3438 80
        $function->parse($this);
3439
3440 80
        return $function;
3441
    }
3442
3443
    /**
3444
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3445
     */
3446 1
    public function CustomFunctionsReturningNumerics()
3447
    {
3448
        // getCustomNumericFunction is case-insensitive
3449 1
        $functionName  = strtolower($this->lexer->lookahead['value']);
3450 1
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3451
3452 1
        $function = is_string($functionClass)
3453 1
            ? new $functionClass($functionName)
3454 1
            : call_user_func($functionClass, $functionName);
3455
3456 1
        $function->parse($this);
3457
3458 1
        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 3
    public function FunctionsReturningDatetime()
3472
    {
3473 3
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3474 3
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3475
3476 3
        $function = new $funcClass($funcNameLower);
3477 3
        $function->parse($this);
3478
3479 3
        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 21
    public function FunctionsReturningStrings()
3512
    {
3513 21
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3514 21
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3515
3516 21
        $function = new $funcClass($funcNameLower);
3517 21
        $function->parse($this);
3518
3519 21
        return $function;
3520
    }
3521
3522
    /**
3523
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3524
     */
3525 1
    public function CustomFunctionsReturningStrings()
3526
    {
3527
        // getCustomStringFunction is case-insensitive
3528 1
        $functionName  = $this->lexer->lookahead['value'];
3529 1
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3530
3531 1
        $function = is_string($functionClass)
3532 1
            ? new $functionClass($functionName)
3533 1
            : call_user_func($functionClass, $functionName);
3534
3535 1
        $function->parse($this);
3536
3537 1
        return $function;
3538
    }
3539
}
3540