Failed Conditions
Pull Request — 2.6 (#7197)
by JHONATAN
09:37
created

Parser::UpdateItem()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
371 95
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
372
373 95
            foreach ($this->customTreeWalkers as $walker) {
374 95
                $treeWalkerChain->addTreeWalker($walker);
375
            }
376
377
            switch (true) {
378 95
                case ($AST instanceof AST\UpdateStatement):
379
                    $treeWalkerChain->walkUpdateStatement($AST);
380
                    break;
381
382 95
                case ($AST instanceof AST\DeleteStatement):
383
                    $treeWalkerChain->walkDeleteStatement($AST);
384
                    break;
385
386 95
                case ($AST instanceof AST\SelectStatement):
387
                default:
388 95
                    $treeWalkerChain->walkSelectStatement($AST);
389
            }
390
391 89
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
392
        }
393
394 799
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
395 799
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
396
397
        // Assign an SQL executor to the parser result
398 799
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
399
400 791
        return $this->parserResult;
401
    }
402
403
    /**
404
     * Fixes order of identification variables.
405
     *
406
     * They have to appear in the select clause in the same order as the
407
     * declarations (from ... x join ... y join ... z ...) appear in the query
408
     * as the hydration process relies on that order for proper operation.
409
     *
410
     * @param AST\SelectStatement|AST\DeleteStatement|AST\UpdateStatement $AST
411
     *
412
     * @return void
413
     */
414 805
    private function fixIdentificationVariableOrder($AST)
415
    {
416 805
        if (count($this->identVariableExpressions) <= 1) {
417 628
            return;
418
        }
419
420 182
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
421 182
            if ( ! isset($this->identVariableExpressions[$dqlAlias])) {
422 8
                continue;
423
            }
424
425 182
            $expr = $this->identVariableExpressions[$dqlAlias];
426 182
            $key  = array_search($expr, $AST->selectClause->selectExpressions);
0 ignored issues
show
Bug introduced by
The property selectClause does not seem to exist on Doctrine\ORM\Query\AST\DeleteStatement.
Loading history...
Bug introduced by
The property selectClause does not seem to exist on Doctrine\ORM\Query\AST\UpdateStatement.
Loading history...
427
428 182
            unset($AST->selectClause->selectExpressions[$key]);
429
430 182
            $AST->selectClause->selectExpressions[] = $expr;
431
        }
432 182
    }
433
434
    /**
435
     * Generates a new syntax error.
436
     *
437
     * @param string     $expected Expected string.
438
     * @param array|null $token    Got token.
439
     *
440
     * @return void
441
     *
442
     * @throws \Doctrine\ORM\Query\QueryException
443
     */
444 18
    public function syntaxError($expected = '', $token = null)
445
    {
446 18
        if ($token === null) {
447 15
            $token = $this->lexer->lookahead;
448
        }
449
450 18
        $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
451
452 18
        $message  = "line 0, col {$tokenPos}: Error: ";
453 18
        $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected ';
454 18
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'";
455
456 18
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
457
    }
458
459
    /**
460
     * Generates a new semantical error.
461
     *
462
     * @param string     $message Optional message.
463
     * @param array|null $token   Optional token.
464
     *
465
     * @return void
466
     *
467
     * @throws \Doctrine\ORM\Query\QueryException
468
     */
469 35
    public function semanticalError($message = '', $token = null)
470
    {
471 35
        if ($token === null) {
472 2
            $token = $this->lexer->lookahead;
473
        }
474
475
        // Minimum exposed chars ahead of token
476 35
        $distance = 12;
477
478
        // Find a position of a final word to display in error string
479 35
        $dql    = $this->query->getDQL();
480 35
        $length = strlen($dql);
481 35
        $pos    = $token['position'] + $distance;
482 35
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
483 35
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
484
485 35
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
486 35
        $tokenStr = substr($dql, $token['position'], $length);
487
488
        // Building informative message
489 35
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
490
491 35
        throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL()));
492
    }
493
494
    /**
495
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
496
     *
497
     * @param boolean $resetPeek Reset peek after finding the closing parenthesis.
498
     *
499
     * @return array
500
     */
501 171
    private function peekBeyondClosingParenthesis($resetPeek = true)
502
    {
503 171
        $token = $this->lexer->peek();
504 171
        $numUnmatched = 1;
505
506 171
        while ($numUnmatched > 0 && $token !== null) {
507 170
            switch ($token['type']) {
508 170
                case Lexer::T_OPEN_PARENTHESIS:
509 45
                    ++$numUnmatched;
510 45
                    break;
511
512 170
                case Lexer::T_CLOSE_PARENTHESIS:
513 170
                    --$numUnmatched;
514 170
                    break;
515
516
                default:
517
                    // Do nothing
518
            }
519
520 170
            $token = $this->lexer->peek();
521
        }
522
523 171
        if ($resetPeek) {
524 150
            $this->lexer->resetPeek();
525
        }
526
527 171
        return $token;
528
    }
529
530
    /**
531
     * Checks if the given token indicates a mathematical operator.
532
     *
533
     * @param array $token
534
     *
535
     * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise.
536
     */
537 359
    private function isMathOperator($token)
538
    {
539 359
        return in_array($token['type'], [Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY]);
540
    }
541
542
    /**
543
     * Checks if the next-next (after lookahead) token starts a function.
544
     *
545
     * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
546
     */
547 404
    private function isFunction()
548
    {
549 404
        $lookaheadType = $this->lexer->lookahead['type'];
550 404
        $peek          = $this->lexer->peek();
551
552 404
        $this->lexer->resetPeek();
553
554 404
        return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
555
    }
556
557
    /**
558
     * Checks whether the given token type indicates an aggregate function.
559
     *
560
     * @param int $tokenType
561
     *
562
     * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
563
     */
564 1
    private function isAggregateFunction($tokenType)
565
    {
566 1
        return in_array($tokenType, [Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT]);
567
    }
568
569
    /**
570
     * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
571
     *
572
     * @return boolean
573
     */
574 304
    private function isNextAllAnySome()
575
    {
576 304
        return in_array($this->lexer->lookahead['type'], [Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME]);
577
    }
578
579
    /**
580
     * Validates that the given <tt>IdentificationVariable</tt> is semantically correct.
581
     * It must exist in query components list.
582
     *
583
     * @return void
584
     */
585 817
    private function processDeferredIdentificationVariables()
586
    {
587 817
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
588 793
            $identVariable = $deferredItem['expression'];
589
590
            // Check if IdentificationVariable exists in queryComponents
591 793
            if ( ! isset($this->queryComponents[$identVariable])) {
592 1
                $this->semanticalError(
593 1
                    "'$identVariable' is not defined.", $deferredItem['token']
594
                );
595
            }
596
597 793
            $qComp = $this->queryComponents[$identVariable];
598
599
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
600 793
            if ( ! isset($qComp['metadata'])) {
601
                $this->semanticalError(
602
                    "'$identVariable' does not point to a Class.", $deferredItem['token']
603
                );
604
            }
605
606
            // Validate if identification variable nesting level is lower or equal than the current one
607 793
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
608 1
                $this->semanticalError(
609 793
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
610
                );
611
            }
612
        }
613 815
    }
614
615
    /**
616
     * Validates that the given <tt>NewObjectExpression</tt>.
617
     *
618
     * @param \Doctrine\ORM\Query\AST\SelectClause $AST
619
     *
620
     * @return void
621
     */
622 28
    private function processDeferredNewObjectExpressions($AST)
623
    {
624 28
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
625 28
            $expression     = $deferredItem['expression'];
626 28
            $token          = $deferredItem['token'];
627 28
            $className      = $expression->className;
628 28
            $args           = $expression->args;
629 28
            $fromClassName  = isset($AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName)
0 ignored issues
show
Bug introduced by
The property fromClause does not seem to exist on Doctrine\ORM\Query\AST\SelectClause.
Loading history...
630 28
                ? $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName
631 28
                : null;
632
633
            // If the namespace is not given then assumes the first FROM entity namespace
634 28
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
635 11
                $namespace  = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
636 11
                $fqcn       = $namespace . '\\' . $className;
637
638 11
                if (class_exists($fqcn)) {
639 11
                    $expression->className  = $fqcn;
640 11
                    $className              = $fqcn;
641
                }
642
            }
643
644 28
            if ( ! class_exists($className)) {
645 1
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
646
            }
647
648 27
            $class = new \ReflectionClass($className);
649
650 27
            if ( ! $class->isInstantiable()) {
651 1
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
652
            }
653
654 26
            if ($class->getConstructor() === null) {
655 1
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
656
            }
657
658 25
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
659 25
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
660
            }
661
        }
662 24
    }
663
664
    /**
665
     * Validates that the given <tt>PartialObjectExpression</tt> is semantically correct.
666
     * It must exist in query components list.
667
     *
668
     * @return void
669
     */
670 11
    private function processDeferredPartialObjectExpressions()
671
    {
672 11
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
673 11
            $expr = $deferredItem['expression'];
674 11
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
675
676 11
            foreach ($expr->partialFieldSet as $field) {
677 11
                if (isset($class->fieldMappings[$field])) {
678 10
                    continue;
679
                }
680
681 3
                if (isset($class->associationMappings[$field]) &&
682 3
                    $class->associationMappings[$field]['isOwningSide'] &&
683 3
                    $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) {
684 2
                    continue;
685
                }
686
687 1
                $this->semanticalError(
688 1
                    "There is no mapped field named '$field' on class " . $class->name . ".", $deferredItem['token']
689
                );
690
            }
691
692 10
            if (array_intersect($class->identifier, $expr->partialFieldSet) != $class->identifier) {
693 1
                $this->semanticalError(
694 1
                    "The partial field selection of class " . $class->name . " must contain the identifier.",
695 10
                    $deferredItem['token']
696
                );
697
            }
698
        }
699 9
    }
700
701
    /**
702
     * Validates that the given <tt>ResultVariable</tt> is semantically correct.
703
     * It must exist in query components list.
704
     *
705
     * @return void
706
     */
707 32
    private function processDeferredResultVariables()
708
    {
709 32
        foreach ($this->deferredResultVariables as $deferredItem) {
710 32
            $resultVariable = $deferredItem['expression'];
711
712
            // Check if ResultVariable exists in queryComponents
713 32
            if ( ! isset($this->queryComponents[$resultVariable])) {
714
                $this->semanticalError(
715
                    "'$resultVariable' is not defined.", $deferredItem['token']
716
                );
717
            }
718
719 32
            $qComp = $this->queryComponents[$resultVariable];
720
721
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
722 32
            if ( ! isset($qComp['resultVariable'])) {
723
                $this->semanticalError(
724
                    "'$resultVariable' does not point to a ResultVariable.", $deferredItem['token']
725
                );
726
            }
727
728
            // Validate if identification variable nesting level is lower or equal than the current one
729 32
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
730
                $this->semanticalError(
731 32
                    "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
732
                );
733
            }
734
        }
735 32
    }
736
737
    /**
738
     * Validates that the given <tt>PathExpression</tt> is semantically correct for grammar rules:
739
     *
740
     * AssociationPathExpression             ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
741
     * SingleValuedPathExpression            ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
742
     * StateFieldPathExpression              ::= IdentificationVariable "." StateField
743
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
744
     * CollectionValuedPathExpression        ::= IdentificationVariable "." CollectionValuedAssociationField
745
     *
746
     * @return void
747
     */
748 601
    private function processDeferredPathExpressions()
749
    {
750 601
        foreach ($this->deferredPathExpressions as $deferredItem) {
751 601
            $pathExpression = $deferredItem['expression'];
752
753 601
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
754 601
            $class = $qComp['metadata'];
755
756 601
            if (($field = $pathExpression->field) === null) {
757 39
                $field = $pathExpression->field = $class->identifier[0];
758
            }
759
760
            // Check if field or association exists
761 601
            if ( ! isset($class->associationMappings[$field]) && ! isset($class->fieldMappings[$field])) {
762 1
                $this->semanticalError(
763 1
                    'Class ' . $class->name . ' has no field or association named ' . $field,
764 1
                    $deferredItem['token']
765
                );
766
            }
767
768 600
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
769
770 600
            if (isset($class->associationMappings[$field])) {
771 87
                $assoc = $class->associationMappings[$field];
772
773 87
                $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
774 66
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
775 87
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
776
            }
777
778
            // Validate if PathExpression is one of the expected types
779 600
            $expectedType = $pathExpression->expectedType;
780
781 600
            if ( ! ($expectedType & $fieldType)) {
782
                // We need to recognize which was expected type(s)
783 2
                $expectedStringTypes = [];
784
785
                // Validate state field type
786 2
                if ($expectedType & AST\PathExpression::TYPE_STATE_FIELD) {
787 1
                    $expectedStringTypes[] = 'StateFieldPathExpression';
788
                }
789
790
                // Validate single valued association (*-to-one)
791 2
                if ($expectedType & AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION) {
792 2
                    $expectedStringTypes[] = 'SingleValuedAssociationField';
793
                }
794
795
                // Validate single valued association (*-to-many)
796 2
                if ($expectedType & AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION) {
797
                    $expectedStringTypes[] = 'CollectionValuedAssociationField';
798
                }
799
800
                // Build the error message
801 2
                $semanticalError  = 'Invalid PathExpression. ';
802 2
                $semanticalError .= (count($expectedStringTypes) == 1)
803 1
                    ? 'Must be a ' . $expectedStringTypes[0] . '.'
804 2
                    : implode(' or ', $expectedStringTypes) . ' expected.';
805
806 2
                $this->semanticalError($semanticalError, $deferredItem['token']);
807
            }
808
809
            // We need to force the type in PathExpression
810 598
            $pathExpression->type = $fieldType;
811
        }
812 598
    }
813
814
    /**
815
     * @return void
816
     */
817 806
    private function processRootEntityAliasSelected()
818
    {
819 806
        if ( ! count($this->identVariableExpressions)) {
820 237
            return;
821
        }
822
823 580
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
824 580
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
825 580
                return;
826
            }
827
        }
828
829 1
        $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
830
    }
831
832
    /**
833
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
834
     *
835
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
836
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
837
     *         \Doctrine\ORM\Query\AST\DeleteStatement
838
     */
839 859
    public function QueryLanguage()
840
    {
841 859
        $statement = null;
842
843 859
        $this->lexer->moveNext();
844
845 859
        switch ($this->lexer->lookahead['type']) {
846 859
            case Lexer::T_SELECT:
847 793
                $statement = $this->SelectStatement();
848 755
                break;
849
850 73
            case Lexer::T_UPDATE:
851 32
                $statement = $this->UpdateStatement();
852 32
                break;
853
854 43
            case Lexer::T_DELETE:
855 42
                $statement = $this->DeleteStatement();
856 42
                break;
857
858
            default:
859 2
                $this->syntaxError('SELECT, UPDATE or DELETE');
860
                break;
861
        }
862
863
        // Check for end of string
864 821
        if ($this->lexer->lookahead !== null) {
865 4
            $this->syntaxError('end of string');
866
        }
867
868 817
        return $statement;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $statement returns the type Doctrine\ORM\Query\AST\U...ery\AST\DeleteStatement which is incompatible with the documented return type Doctrine\ORM\Query\AST\SelectStatement.
Loading history...
869
    }
870
871
    /**
872
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
873
     *
874
     * @return \Doctrine\ORM\Query\AST\SelectStatement
875
     */
876 793
    public function SelectStatement()
877
    {
878 793
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
879
880 759
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
881 756
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
882 755
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
883 755
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
884
885 755
        return $selectStatement;
886
    }
887
888
    /**
889
     * UpdateStatement ::= UpdateClause [WhereClause]
890
     *
891
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
892
     */
893 32
    public function UpdateStatement()
894
    {
895 32
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
896
897 32
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
898
899 32
        return $updateStatement;
900
    }
901
902
    /**
903
     * DeleteStatement ::= DeleteClause [WhereClause]
904
     *
905
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
906
     */
907 42
    public function DeleteStatement()
908
    {
909 42
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
910
911 42
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
912
913 42
        return $deleteStatement;
914
    }
915
916
    /**
917
     * IdentificationVariable ::= identifier
918
     *
919
     * @return string
920
     */
921 824
    public function IdentificationVariable()
922
    {
923 824
        $this->match(Lexer::T_IDENTIFIER);
924
925 824
        $identVariable = $this->lexer->token['value'];
926
927 824
        $this->deferredIdentificationVariables[] = [
928 824
            'expression'   => $identVariable,
929 824
            'nestingLevel' => $this->nestingLevel,
930 824
            'token'        => $this->lexer->token,
931
        ];
932
933 824
        return $identVariable;
934
    }
935
936
    /**
937
     * AliasIdentificationVariable = identifier
938
     *
939
     * @return string
940
     */
941 827
    public function AliasIdentificationVariable()
942
    {
943 827
        $this->match(Lexer::T_IDENTIFIER);
944
945 827
        $aliasIdentVariable = $this->lexer->token['value'];
946 827
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
947
948 827
        if ($exists) {
949 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
950
        }
951
952 827
        return $aliasIdentVariable;
953
    }
954
955
    /**
956
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
957
     *
958
     * @return string
959
     */
960 849
    public function AbstractSchemaName()
961
    {
962 849
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
963 831
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
964
965 831
            return $this->lexer->token['value'];
966
        }
967
968 29
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
969 19
            $this->match(Lexer::T_IDENTIFIER);
970
971 19
            return $this->lexer->token['value'];
972
        }
973
974 11
        $this->match(Lexer::T_ALIASED_NAME);
975
976 10
        [$namespaceAlias, $simpleClassName] = explode(':', $this->lexer->token['value']);
977
978 10
        return $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
979
    }
980
981
    /**
982
     * Validates an AbstractSchemaName, making sure the class exists.
983
     *
984
     * @param string $schemaName The name to validate.
985
     *
986
     * @throws QueryException if the name does not exist.
987
     */
988 843
    private function validateAbstractSchemaName($schemaName)
989
    {
990 843
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
991 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
992
        }
993 828
    }
994
995
    /**
996
     * AliasResultVariable ::= identifier
997
     *
998
     * @return string
999
     */
1000 132
    public function AliasResultVariable()
1001
    {
1002 132
        $this->match(Lexer::T_IDENTIFIER);
1003
1004 128
        $resultVariable = $this->lexer->token['value'];
1005 128
        $exists = isset($this->queryComponents[$resultVariable]);
1006
1007 128
        if ($exists) {
1008 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1009
        }
1010
1011 128
        return $resultVariable;
1012
    }
1013
1014
    /**
1015
     * ResultVariable ::= identifier
1016
     *
1017
     * @return string
1018
     */
1019 32
    public function ResultVariable()
1020
    {
1021 32
        $this->match(Lexer::T_IDENTIFIER);
1022
1023 32
        $resultVariable = $this->lexer->token['value'];
1024
1025
        // Defer ResultVariable validation
1026 32
        $this->deferredResultVariables[] = [
1027 32
            'expression'   => $resultVariable,
1028 32
            'nestingLevel' => $this->nestingLevel,
1029 32
            'token'        => $this->lexer->token,
1030
        ];
1031
1032 32
        return $resultVariable;
1033
    }
1034
1035
    /**
1036
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1037
     *
1038
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1039
     */
1040 259
    public function JoinAssociationPathExpression()
1041
    {
1042 259
        $identVariable = $this->IdentificationVariable();
1043
1044 259
        if ( ! isset($this->queryComponents[$identVariable])) {
1045
            $this->semanticalError(
1046
                'Identification Variable ' . $identVariable .' used in join path expression but was not defined before.'
1047
            );
1048
        }
1049
1050 259
        $this->match(Lexer::T_DOT);
1051 259
        $this->match(Lexer::T_IDENTIFIER);
1052
1053 259
        $field = $this->lexer->token['value'];
1054
1055
        // Validate association field
1056 259
        $qComp = $this->queryComponents[$identVariable];
1057 259
        $class = $qComp['metadata'];
1058
1059 259
        if ( ! $class->hasAssociation($field)) {
1060
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
1061
        }
1062
1063 259
        return new AST\JoinAssociationPathExpression($identVariable, $field);
1064
    }
1065
1066
    /**
1067
     * Parses an arbitrary path expression and defers semantical validation
1068
     * based on expected types.
1069
     *
1070
     * PathExpression ::= IdentificationVariable {"." identifier}*
1071
     *
1072
     * @param integer $expectedTypes
1073
     *
1074
     * @return \Doctrine\ORM\Query\AST\PathExpression
1075
     */
1076 611
    public function PathExpression($expectedTypes)
1077
    {
1078 611
        $identVariable = $this->IdentificationVariable();
1079 611
        $field = null;
1080
1081 611
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1082 604
            $this->match(Lexer::T_DOT);
1083 604
            $this->match(Lexer::T_IDENTIFIER);
1084
1085 604
            $field = $this->lexer->token['value'];
1086
1087 604
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1088 2
                $this->match(Lexer::T_DOT);
1089 2
                $this->match(Lexer::T_IDENTIFIER);
1090 2
                $field .= '.'.$this->lexer->token['value'];
1091
            }
1092
        }
1093
1094
        // Creating AST node
1095 611
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1096
1097
        // Defer PathExpression validation if requested to be deferred
1098 611
        $this->deferredPathExpressions[] = [
1099 611
            'expression'   => $pathExpr,
1100 611
            'nestingLevel' => $this->nestingLevel,
1101 611
            'token'        => $this->lexer->token,
1102
        ];
1103
1104 611
        return $pathExpr;
1105
    }
1106
1107
    /**
1108
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
1109
     *
1110
     * @return \Doctrine\ORM\Query\AST\PathExpression
1111
     */
1112
    public function AssociationPathExpression()
1113
    {
1114
        return $this->PathExpression(
1115
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
1116
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
1117
        );
1118
    }
1119
1120
    /**
1121
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
1122
     *
1123
     * @return \Doctrine\ORM\Query\AST\PathExpression
1124
     */
1125 519
    public function SingleValuedPathExpression()
1126
    {
1127 519
        return $this->PathExpression(
1128 519
            AST\PathExpression::TYPE_STATE_FIELD |
1129 519
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1130
        );
1131
    }
1132
1133
    /**
1134
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1135
     *
1136
     * @return \Doctrine\ORM\Query\AST\PathExpression
1137
     */
1138 204
    public function StateFieldPathExpression()
1139
    {
1140 204
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
1141
    }
1142
1143
    /**
1144
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
1145
     *
1146
     * @return \Doctrine\ORM\Query\AST\PathExpression
1147
     */
1148 9
    public function SingleValuedAssociationPathExpression()
1149
    {
1150 9
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
1151
    }
1152
1153
    /**
1154
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
1155
     *
1156
     * @return \Doctrine\ORM\Query\AST\PathExpression
1157
     */
1158 21
    public function CollectionValuedPathExpression()
1159
    {
1160 21
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
1161
    }
1162
1163
    /**
1164
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
1165
     *
1166
     * @return \Doctrine\ORM\Query\AST\SelectClause
1167
     */
1168 793
    public function SelectClause()
1169
    {
1170 793
        $isDistinct = false;
1171 793
        $this->match(Lexer::T_SELECT);
1172
1173
        // Check for DISTINCT
1174 793
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1175 6
            $this->match(Lexer::T_DISTINCT);
1176
1177 6
            $isDistinct = true;
1178
        }
1179
1180
        // Process SelectExpressions (1..N)
1181 793
        $selectExpressions = [];
1182 793
        $selectExpressions[] = $this->SelectExpression();
1183
1184 785
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1185 300
            $this->match(Lexer::T_COMMA);
1186
1187 300
            $selectExpressions[] = $this->SelectExpression();
1188
        }
1189
1190 784
        return new AST\SelectClause($selectExpressions, $isDistinct);
1191
    }
1192
1193
    /**
1194
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1195
     *
1196
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1197
     */
1198 49
    public function SimpleSelectClause()
1199
    {
1200 49
        $isDistinct = false;
1201 49
        $this->match(Lexer::T_SELECT);
1202
1203 49
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1204
            $this->match(Lexer::T_DISTINCT);
1205
1206
            $isDistinct = true;
1207
        }
1208
1209 49
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1210
    }
1211
1212
    /**
1213
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1214
     *
1215
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1216
     */
1217 32
    public function UpdateClause()
1218
    {
1219 32
        $this->match(Lexer::T_UPDATE);
1220
1221 32
        $token = $this->lexer->lookahead;
1222 32
        $abstractSchemaName = $this->AbstractSchemaName();
1223
1224 32
        $this->validateAbstractSchemaName($abstractSchemaName);
1225
1226 32
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1227 2
            $this->match(Lexer::T_AS);
1228
        }
1229
1230 32
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1231
1232 32
        $class = $this->em->getClassMetadata($abstractSchemaName);
1233
1234
        // Building queryComponent
1235
        $queryComponent = [
1236 32
            'metadata'     => $class,
1237
            'parent'       => null,
1238
            'relation'     => null,
1239
            'map'          => null,
1240 32
            'nestingLevel' => $this->nestingLevel,
1241 32
            'token'        => $token,
1242
        ];
1243
1244 32
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1245
1246 32
        $this->match(Lexer::T_SET);
1247
1248 32
        $updateItems = [];
1249 32
        $updateItems[] = $this->UpdateItem();
1250
1251 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1252 5
            $this->match(Lexer::T_COMMA);
1253
1254 5
            $updateItems[] = $this->UpdateItem();
1255
        }
1256
1257 32
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1258 32
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1259
1260 32
        return $updateClause;
1261
    }
1262
1263
    /**
1264
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1265
     *
1266
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1267
     */
1268 42
    public function DeleteClause()
1269
    {
1270 42
        $this->match(Lexer::T_DELETE);
1271
1272 42
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1273 9
            $this->match(Lexer::T_FROM);
1274
        }
1275
1276 42
        $token = $this->lexer->lookahead;
1277 42
        $abstractSchemaName = $this->AbstractSchemaName();
1278
1279 42
        $this->validateAbstractSchemaName($abstractSchemaName);
1280
1281 42
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1282
1283 42
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1284 1
            $this->match(Lexer::T_AS);
1285
        }
1286
1287 42
        $aliasIdentificationVariable = $this->lexer->isNextToken(Lexer::T_IDENTIFIER)
1288 40
            ? $this->AliasIdentificationVariable()
1289 42
            : 'alias_should_have_been_set';
1290
1291 42
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1292 42
        $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1293
1294
        // Building queryComponent
1295
        $queryComponent = [
1296 42
            'metadata'     => $class,
1297
            'parent'       => null,
1298
            'relation'     => null,
1299
            'map'          => null,
1300 42
            'nestingLevel' => $this->nestingLevel,
1301 42
            'token'        => $token,
1302
        ];
1303
1304 42
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1305
1306 42
        return $deleteClause;
1307
    }
1308
1309
    /**
1310
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1311
     *
1312
     * @return \Doctrine\ORM\Query\AST\FromClause
1313
     */
1314 784
    public function FromClause()
1315
    {
1316 784
        $this->match(Lexer::T_FROM);
1317
1318 779
        $identificationVariableDeclarations = [];
1319 779
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1320
1321 759
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1322 6
            $this->match(Lexer::T_COMMA);
1323
1324 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1325
        }
1326
1327 759
        return new AST\FromClause($identificationVariableDeclarations);
1328
    }
1329
1330
    /**
1331
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1332
     *
1333
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1334
     */
1335 49
    public function SubselectFromClause()
1336
    {
1337 49
        $this->match(Lexer::T_FROM);
1338
1339 49
        $identificationVariables = [];
1340 49
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1341
1342 48
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1343
            $this->match(Lexer::T_COMMA);
1344
1345
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1346
        }
1347
1348 48
        return new AST\SubselectFromClause($identificationVariables);
1349
    }
1350
1351
    /**
1352
     * WhereClause ::= "WHERE" ConditionalExpression
1353
     *
1354
     * @return \Doctrine\ORM\Query\AST\WhereClause
1355
     */
1356 346
    public function WhereClause()
1357
    {
1358 346
        $this->match(Lexer::T_WHERE);
1359
1360 346
        return new AST\WhereClause($this->ConditionalExpression());
1361
    }
1362
1363
    /**
1364
     * HavingClause ::= "HAVING" ConditionalExpression
1365
     *
1366
     * @return \Doctrine\ORM\Query\AST\HavingClause
1367
     */
1368 21
    public function HavingClause()
1369
    {
1370 21
        $this->match(Lexer::T_HAVING);
1371
1372 21
        return new AST\HavingClause($this->ConditionalExpression());
1373
    }
1374
1375
    /**
1376
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1377
     *
1378
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1379
     */
1380 33
    public function GroupByClause()
1381
    {
1382 33
        $this->match(Lexer::T_GROUP);
1383 33
        $this->match(Lexer::T_BY);
1384
1385 33
        $groupByItems = [$this->GroupByItem()];
1386
1387 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1388 8
            $this->match(Lexer::T_COMMA);
1389
1390 8
            $groupByItems[] = $this->GroupByItem();
1391
        }
1392
1393 32
        return new AST\GroupByClause($groupByItems);
1394
    }
1395
1396
    /**
1397
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1398
     *
1399
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1400
     */
1401 182
    public function OrderByClause()
1402
    {
1403 182
        $this->match(Lexer::T_ORDER);
1404 182
        $this->match(Lexer::T_BY);
1405
1406 182
        $orderByItems = [];
1407 182
        $orderByItems[] = $this->OrderByItem();
1408
1409 182
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1410 15
            $this->match(Lexer::T_COMMA);
1411
1412 15
            $orderByItems[] = $this->OrderByItem();
1413
        }
1414
1415 182
        return new AST\OrderByClause($orderByItems);
1416
    }
1417
1418
    /**
1419
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1420
     *
1421
     * @return \Doctrine\ORM\Query\AST\Subselect
1422
     */
1423 49
    public function Subselect()
1424
    {
1425
        // Increase query nesting level
1426 49
        $this->nestingLevel++;
1427
1428 49
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1429
1430 48
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1431 48
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1432 48
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1433 48
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1434
1435
        // Decrease query nesting level
1436 48
        $this->nestingLevel--;
1437
1438 48
        return $subselect;
1439
    }
1440
1441
    /**
1442
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1443
     *
1444
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1445
     */
1446 32
    public function UpdateItem()
1447
    {
1448 32
        $pathExpr = $this->SingleValuedPathExpression();
1449
1450 32
        $this->match(Lexer::T_EQUALS);
1451
1452 32
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1453
1454 32
        return $updateItem;
1455
    }
1456
1457
    /**
1458
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1459
     *
1460
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1461
     */
1462 33
    public function GroupByItem()
1463
    {
1464
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1465 33
        $glimpse = $this->lexer->glimpse();
1466
1467 33
        if ($glimpse['type'] === Lexer::T_DOT) {
1468 14
            return $this->SingleValuedPathExpression();
1469
        }
1470
1471
        // Still need to decide between IdentificationVariable or ResultVariable
1472 19
        $lookaheadValue = $this->lexer->lookahead['value'];
1473
1474 19
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1475 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1476
        }
1477
1478 18
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1479 16
            ? $this->IdentificationVariable()
1480 18
            : $this->ResultVariable();
1481
    }
1482
1483
    /**
1484
     * OrderByItem ::= (
1485
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1486
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1487
     * ) ["ASC" | "DESC"]
1488
     *
1489
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1490
     */
1491 182
    public function OrderByItem()
1492
    {
1493 182
        $this->lexer->peek(); // lookahead => '.'
1494 182
        $this->lexer->peek(); // lookahead => token after '.'
1495
1496 182
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1497
1498 182
        $this->lexer->resetPeek();
1499
1500 182
        $glimpse = $this->lexer->glimpse();
1501
1502
        switch (true) {
1503 182
            case ($this->isFunction()):
1504 2
                $expr = $this->FunctionDeclaration();
1505 2
                break;
1506
1507 180
            case ($this->isMathOperator($peek)):
1508 25
                $expr = $this->SimpleArithmeticExpression();
1509 25
                break;
1510
1511 156
            case ($glimpse['type'] === Lexer::T_DOT):
1512 141
                $expr = $this->SingleValuedPathExpression();
1513 141
                break;
1514
1515 19
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1516 2
                $expr = $this->ScalarExpression();
1517 2
                break;
1518
1519
            default:
1520 17
                $expr = $this->ResultVariable();
1521 17
                break;
1522
        }
1523
1524 182
        $type = 'ASC';
1525 182
        $item = new AST\OrderByItem($expr);
1526
1527
        switch (true) {
1528 182
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1529 95
                $this->match(Lexer::T_DESC);
1530 95
                $type = 'DESC';
1531 95
                break;
1532
1533 154
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1534 97
                $this->match(Lexer::T_ASC);
1535 97
                break;
1536
1537
            default:
1538
                // Do nothing
1539
        }
1540
1541 182
        $item->type = $type;
1542
1543 182
        return $item;
1544
    }
1545
1546
    /**
1547
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1548
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1549
     *
1550
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1551
     * grammar that needs to be supported:
1552
     *
1553
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1554
     *
1555
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1556
     *
1557
     * @return AST\ArithmeticExpression
1558
     */
1559 32
    public function NewValue()
1560
    {
1561 32
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1562 1
            $this->match(Lexer::T_NULL);
1563
1564 1
            return null;
1565
        }
1566
1567 31
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1568 19
            $this->match(Lexer::T_INPUT_PARAMETER);
1569
1570 19
            return new AST\InputParameter($this->lexer->token['value']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Doctrine\ORM\...>lexer->token['value']) returns the type Doctrine\ORM\Query\AST\InputParameter which is incompatible with the documented return type Doctrine\ORM\Query\AST\ArithmeticExpression.
Loading history...
1571
        }
1572
1573 12
        return $this->ArithmeticExpression();
1574
    }
1575
1576
    /**
1577
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1578
     *
1579
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1580
     */
1581 781
    public function IdentificationVariableDeclaration()
1582
    {
1583 781
        $joins                    = [];
1584 781
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1585 764
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1586 8
            ? $this->IndexBy()
1587 764
            : null;
1588
1589 764
        $rangeVariableDeclaration->isRoot = true;
1590
1591
        while (
1592 764
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1593 764
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1594 764
            $this->lexer->isNextToken(Lexer::T_JOIN)
1595
        ) {
1596 281
            $joins[] = $this->Join();
1597
        }
1598
1599 761
        return new AST\IdentificationVariableDeclaration(
1600 761
            $rangeVariableDeclaration, $indexBy, $joins
1601
        );
1602
    }
1603
1604
    /**
1605
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1606
     *
1607
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1608
     * Desired EBNF support:
1609
     *
1610
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1611
     *
1612
     * It demands that entire SQL generation to become programmatical. This is
1613
     * needed because association based subselect requires "WHERE" conditional
1614
     * expressions to be injected, but there is no scope to do that. Only scope
1615
     * accessible is "FROM", prohibiting an easy implementation without larger
1616
     * changes.}
1617
     *
1618
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1619
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1620
     */
1621 49
    public function SubselectIdentificationVariableDeclaration()
1622
    {
1623
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1624
        NOT YET IMPLEMENTED!
1625
1626
        $glimpse = $this->lexer->glimpse();
1627
1628
        if ($glimpse['type'] == Lexer::T_DOT) {
1629
            $associationPathExpression = $this->AssociationPathExpression();
1630
1631
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1632
                $this->match(Lexer::T_AS);
1633
            }
1634
1635
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1636
            $identificationVariable      = $associationPathExpression->identificationVariable;
1637
            $field                       = $associationPathExpression->associationField;
1638
1639
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1640
            $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1641
1642
            // Building queryComponent
1643
            $joinQueryComponent = array(
1644
                'metadata'     => $targetClass,
1645
                'parent'       => $identificationVariable,
1646
                'relation'     => $class->getAssociationMapping($field),
1647
                'map'          => null,
1648
                'nestingLevel' => $this->nestingLevel,
1649
                'token'        => $this->lexer->lookahead
1650
            );
1651
1652
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1653
1654
            return new AST\SubselectIdentificationVariableDeclaration(
1655
                $associationPathExpression, $aliasIdentificationVariable
1656
            );
1657
        }
1658
        */
1659
1660 49
        return $this->IdentificationVariableDeclaration();
1661
    }
1662
1663
    /**
1664
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1665
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1666
     *          ["WITH" ConditionalExpression]
1667
     *
1668
     * @return \Doctrine\ORM\Query\AST\Join
1669
     */
1670 281
    public function Join()
1671
    {
1672
        // Check Join type
1673 281
        $joinType = AST\Join::JOIN_TYPE_INNER;
1674
1675
        switch (true) {
1676 281
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1677 68
                $this->match(Lexer::T_LEFT);
1678
1679 68
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1680
1681
                // Possible LEFT OUTER join
1682 68
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1683
                    $this->match(Lexer::T_OUTER);
1684
1685
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1686
                }
1687 68
                break;
1688
1689 217
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1690 21
                $this->match(Lexer::T_INNER);
1691 21
                break;
1692
1693
            default:
1694
                // Do nothing
1695
        }
1696
1697 281
        $this->match(Lexer::T_JOIN);
1698
1699 281
        $next            = $this->lexer->glimpse();
1700 281
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1701 278
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1702 278
        $join            = new AST\Join($joinType, $joinDeclaration);
1703
1704
        // Describe non-root join declaration
1705 278
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1706 22
            $joinDeclaration->isRoot = false;
1707
        }
1708
1709
        // Check for ad-hoc Join conditions
1710 278
        if ($adhocConditions) {
1711 24
            $this->match(Lexer::T_WITH);
1712
1713 24
            $join->conditionalExpression = $this->ConditionalExpression();
1714
        }
1715
1716 278
        return $join;
1717
    }
1718
1719
    /**
1720
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1721
     *
1722
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1723
     *
1724
     * @throws QueryException
1725
     */
1726 781
    public function RangeVariableDeclaration()
1727
    {
1728 781
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $this->lexer->glimpse()['type'] === Lexer::T_SELECT) {
1729 2
            $this->semanticalError('Subquery is not supported here', $this->lexer->token);
1730
        }
1731
1732 780
        $abstractSchemaName = $this->AbstractSchemaName();
1733
1734 779
        $this->validateAbstractSchemaName($abstractSchemaName);
1735
1736 764
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1737 6
            $this->match(Lexer::T_AS);
1738
        }
1739
1740 764
        $token = $this->lexer->lookahead;
1741 764
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1742 764
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1743
1744
        // Building queryComponent
1745
        $queryComponent = [
1746 764
            'metadata'     => $classMetadata,
1747
            'parent'       => null,
1748
            'relation'     => null,
1749
            'map'          => null,
1750 764
            'nestingLevel' => $this->nestingLevel,
1751 764
            'token'        => $token
1752
        ];
1753
1754 764
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1755
1756 764
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1757
    }
1758
1759
    /**
1760
     * JoinAssociationDeclaration ::= JoinAssociationPathExpression ["AS"] AliasIdentificationVariable [IndexBy]
1761
     *
1762
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1763
     */
1764 259
    public function JoinAssociationDeclaration()
1765
    {
1766 259
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1767
1768 259
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1769 5
            $this->match(Lexer::T_AS);
1770
        }
1771
1772 259
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1773 257
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1774
1775 257
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1776 257
        $field                  = $joinAssociationPathExpression->associationField;
1777
1778 257
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1779 257
        $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1780
1781
        // Building queryComponent
1782
        $joinQueryComponent = [
1783 257
            'metadata'     => $targetClass,
1784 257
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1785 257
            'relation'     => $class->getAssociationMapping($field),
1786
            'map'          => null,
1787 257
            'nestingLevel' => $this->nestingLevel,
1788 257
            'token'        => $this->lexer->lookahead
1789
        ];
1790
1791 257
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1792
1793 257
        return new AST\JoinAssociationDeclaration($joinAssociationPathExpression, $aliasIdentificationVariable, $indexBy);
1794
    }
1795
1796
    /**
1797
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1798
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1799
     *
1800
     * @return \Doctrine\ORM\Query\AST\PartialObjectExpression
1801
     */
1802 11
    public function PartialObjectExpression()
1803
    {
1804 11
        $this->match(Lexer::T_PARTIAL);
1805
1806 11
        $partialFieldSet = [];
1807
1808 11
        $identificationVariable = $this->IdentificationVariable();
1809
1810 11
        $this->match(Lexer::T_DOT);
1811 11
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1812 11
        $this->match(Lexer::T_IDENTIFIER);
1813
1814 11
        $field = $this->lexer->token['value'];
1815
1816
        // First field in partial expression might be embeddable property
1817 11
        while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1818 1
            $this->match(Lexer::T_DOT);
1819 1
            $this->match(Lexer::T_IDENTIFIER);
1820 1
            $field .= '.'.$this->lexer->token['value'];
1821
        }
1822
1823 11
        $partialFieldSet[] = $field;
1824
1825 11
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1826 9
            $this->match(Lexer::T_COMMA);
1827 9
            $this->match(Lexer::T_IDENTIFIER);
1828
1829 9
            $field = $this->lexer->token['value'];
1830
1831 9
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
1832 2
                $this->match(Lexer::T_DOT);
1833 2
                $this->match(Lexer::T_IDENTIFIER);
1834 2
                $field .= '.'.$this->lexer->token['value'];
1835
            }
1836
1837 9
            $partialFieldSet[] = $field;
1838
        }
1839
1840 11
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1841
1842 11
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1843
1844
        // Defer PartialObjectExpression validation
1845 11
        $this->deferredPartialObjectExpressions[] = [
1846 11
            'expression'   => $partialObjectExpression,
1847 11
            'nestingLevel' => $this->nestingLevel,
1848 11
            'token'        => $this->lexer->token,
1849
        ];
1850
1851 11
        return $partialObjectExpression;
1852
    }
1853
1854
    /**
1855
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1856
     *
1857
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1858
     */
1859 28
    public function NewObjectExpression()
1860
    {
1861 28
        $this->match(Lexer::T_NEW);
1862
1863 28
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1864 28
        $token = $this->lexer->token;
1865
1866 28
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1867
1868 28
        $args[] = $this->NewObjectArg();
0 ignored issues
show
Comprehensibility Best Practice introduced by
$args was never initialized. Although not strictly required by PHP, it is generally a good practice to add $args = array(); before regardless.
Loading history...
1869
1870 28
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1871 24
            $this->match(Lexer::T_COMMA);
1872
1873 24
            $args[] = $this->NewObjectArg();
1874
        }
1875
1876 28
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1877
1878 28
        $expression = new AST\NewObjectExpression($className, $args);
1879
1880
        // Defer NewObjectExpression validation
1881 28
        $this->deferredNewObjectExpressions[] = [
1882 28
            'token'        => $token,
1883 28
            'expression'   => $expression,
1884 28
            'nestingLevel' => $this->nestingLevel,
1885
        ];
1886
1887 28
        return $expression;
1888
    }
1889
1890
    /**
1891
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1892
     *
1893
     * @return mixed
1894
     */
1895 28
    public function NewObjectArg()
1896
    {
1897 28
        $token = $this->lexer->lookahead;
1898 28
        $peek  = $this->lexer->glimpse();
1899
1900 28
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1901 2
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1902 2
            $expression = $this->Subselect();
1903 2
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1904
1905 2
            return $expression;
1906
        }
1907
1908 28
        return $this->ScalarExpression();
1909
    }
1910
1911
    /**
1912
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1913
     *
1914
     * @return \Doctrine\ORM\Query\AST\IndexBy
1915
     */
1916 12
    public function IndexBy()
1917
    {
1918 12
        $this->match(Lexer::T_INDEX);
1919 12
        $this->match(Lexer::T_BY);
1920 12
        $pathExpr = $this->StateFieldPathExpression();
1921
1922
        // Add the INDEX BY info to the query component
1923 12
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1924
1925 12
        return new AST\IndexBy($pathExpr);
1926
    }
1927
1928
    /**
1929
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1930
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1931
     *                      InstanceOfExpression
1932
     *
1933
     * @return mixed One of the possible expressions or subexpressions.
1934
     */
1935 162
    public function ScalarExpression()
1936
    {
1937 162
        $lookahead = $this->lexer->lookahead['type'];
1938 162
        $peek      = $this->lexer->glimpse();
1939
1940
        switch (true) {
1941 162
            case ($lookahead === Lexer::T_INTEGER):
1942 159
            case ($lookahead === Lexer::T_FLOAT):
1943
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1944 159
            case ($lookahead === Lexer::T_MINUS):
1945 159
            case ($lookahead === Lexer::T_PLUS):
1946 17
                return $this->SimpleArithmeticExpression();
1947
1948 159
            case ($lookahead === Lexer::T_STRING):
1949 13
                return $this->StringPrimary();
1950
1951 157
            case ($lookahead === Lexer::T_TRUE):
1952 157
            case ($lookahead === Lexer::T_FALSE):
1953 3
                $this->match($lookahead);
1954
1955 3
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1956
1957 157
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1958
                switch (true) {
1959 1
                    case $this->isMathOperator($peek):
1960
                        // :param + u.value
1961 1
                        return $this->SimpleArithmeticExpression();
1962
                    default:
1963
                        return $this->InputParameter();
1964
                }
1965
1966 157
            case ($lookahead === Lexer::T_CASE):
1967 153
            case ($lookahead === Lexer::T_COALESCE):
1968 153
            case ($lookahead === Lexer::T_NULLIF):
1969
                // Since NULLIF and COALESCE can be identified as a function,
1970
                // we need to check these before checking for FunctionDeclaration
1971 8
                return $this->CaseExpression();
1972
1973 153
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1974 4
                return $this->SimpleArithmeticExpression();
1975
1976
            // this check must be done before checking for a filed path expression
1977 150
            case ($this->isFunction()):
1978 27
                $this->lexer->peek(); // "("
1979
1980
                switch (true) {
1981 27
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1982
                        // SUM(u.id) + COUNT(u.id)
1983 7
                        return $this->SimpleArithmeticExpression();
1984
1985
                    default:
1986
                        // IDENTITY(u)
1987 22
                        return $this->FunctionDeclaration();
1988
                }
1989
1990
                break;
1991
            // it is no function, so it must be a field path
1992 131
            case ($lookahead === Lexer::T_IDENTIFIER):
1993 131
                $this->lexer->peek(); // lookahead => '.'
1994 131
                $this->lexer->peek(); // lookahead => token after '.'
1995 131
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1996 131
                $this->lexer->resetPeek();
1997
1998 131
                if ($this->isMathOperator($peek)) {
1999 7
                    return $this->SimpleArithmeticExpression();
2000
                }
2001
2002 126
                return $this->StateFieldPathExpression();
2003
2004
            default:
2005
                $this->syntaxError();
2006
        }
2007
    }
2008
2009
    /**
2010
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
2011
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2012
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2013
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2014
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2015
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2016
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2017
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2018
     *
2019
     * @return mixed One of the possible expressions or subexpressions.
2020
     */
2021 19
    public function CaseExpression()
2022
    {
2023 19
        $lookahead = $this->lexer->lookahead['type'];
2024
2025
        switch ($lookahead) {
2026 19
            case Lexer::T_NULLIF:
2027 5
                return $this->NullIfExpression();
2028
2029 16
            case Lexer::T_COALESCE:
2030 2
                return $this->CoalesceExpression();
2031
2032 14
            case Lexer::T_CASE:
2033 14
                $this->lexer->resetPeek();
2034 14
                $peek = $this->lexer->peek();
2035
2036 14
                if ($peek['type'] === Lexer::T_WHEN) {
2037 9
                    return $this->GeneralCaseExpression();
2038
                }
2039
2040 5
                return $this->SimpleCaseExpression();
2041
2042
            default:
2043
                // Do nothing
2044
                break;
2045
        }
2046
2047
        $this->syntaxError();
2048
    }
2049
2050
    /**
2051
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
2052
     *
2053
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
2054
     */
2055 3
    public function CoalesceExpression()
2056
    {
2057 3
        $this->match(Lexer::T_COALESCE);
2058 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2059
2060
        // Process ScalarExpressions (1..N)
2061 3
        $scalarExpressions = [];
2062 3
        $scalarExpressions[] = $this->ScalarExpression();
2063
2064 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2065 3
            $this->match(Lexer::T_COMMA);
2066
2067 3
            $scalarExpressions[] = $this->ScalarExpression();
2068
        }
2069
2070 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2071
2072 3
        return new AST\CoalesceExpression($scalarExpressions);
2073
    }
2074
2075
    /**
2076
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
2077
     *
2078
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
2079
     */
2080 5
    public function NullIfExpression()
2081
    {
2082 5
        $this->match(Lexer::T_NULLIF);
2083 5
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2084
2085 5
        $firstExpression = $this->ScalarExpression();
2086 5
        $this->match(Lexer::T_COMMA);
2087 5
        $secondExpression = $this->ScalarExpression();
2088
2089 5
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2090
2091 5
        return new AST\NullIfExpression($firstExpression, $secondExpression);
2092
    }
2093
2094
    /**
2095
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
2096
     *
2097
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
2098
     */
2099 9
    public function GeneralCaseExpression()
2100
    {
2101 9
        $this->match(Lexer::T_CASE);
2102
2103
        // Process WhenClause (1..N)
2104 9
        $whenClauses = [];
2105
2106
        do {
2107 9
            $whenClauses[] = $this->WhenClause();
2108 9
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2109
2110 9
        $this->match(Lexer::T_ELSE);
2111 9
        $scalarExpression = $this->ScalarExpression();
2112 9
        $this->match(Lexer::T_END);
2113
2114 9
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
2115
    }
2116
2117
    /**
2118
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
2119
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
2120
     *
2121
     * @return AST\SimpleCaseExpression
2122
     */
2123 5
    public function SimpleCaseExpression()
2124
    {
2125 5
        $this->match(Lexer::T_CASE);
2126 5
        $caseOperand = $this->StateFieldPathExpression();
2127
2128
        // Process SimpleWhenClause (1..N)
2129 5
        $simpleWhenClauses = [];
2130
2131
        do {
2132 5
            $simpleWhenClauses[] = $this->SimpleWhenClause();
2133 5
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2134
2135 5
        $this->match(Lexer::T_ELSE);
2136 5
        $scalarExpression = $this->ScalarExpression();
2137 5
        $this->match(Lexer::T_END);
2138
2139 5
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
2140
    }
2141
2142
    /**
2143
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
2144
     *
2145
     * @return \Doctrine\ORM\Query\AST\WhenClause
2146
     */
2147 9
    public function WhenClause()
2148
    {
2149 9
        $this->match(Lexer::T_WHEN);
2150 9
        $conditionalExpression = $this->ConditionalExpression();
2151 9
        $this->match(Lexer::T_THEN);
2152
2153 9
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
2154
    }
2155
2156
    /**
2157
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
2158
     *
2159
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
2160
     */
2161 5
    public function SimpleWhenClause()
2162
    {
2163 5
        $this->match(Lexer::T_WHEN);
2164 5
        $conditionalExpression = $this->ScalarExpression();
2165 5
        $this->match(Lexer::T_THEN);
2166
2167 5
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
2168
    }
2169
2170
    /**
2171
     * SelectExpression ::= (
2172
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
2173
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
2174
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
2175
     *
2176
     * @return \Doctrine\ORM\Query\AST\SelectExpression
2177
     */
2178 793
    public function SelectExpression()
2179
    {
2180 793
        $expression    = null;
2181 793
        $identVariable = null;
2182 793
        $peek          = $this->lexer->glimpse();
2183 793
        $lookaheadType = $this->lexer->lookahead['type'];
2184
2185
        switch (true) {
2186
            // ScalarExpression (u.name)
2187 793
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2188 103
                $expression = $this->ScalarExpression();
2189 103
                break;
2190
2191
            // IdentificationVariable (u)
2192 733
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2193 606
                $expression = $identVariable = $this->IdentificationVariable();
2194 606
                break;
2195
2196
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2197 190
            case ($lookaheadType === Lexer::T_CASE):
2198 185
            case ($lookaheadType === Lexer::T_COALESCE):
2199 183
            case ($lookaheadType === Lexer::T_NULLIF):
2200 9
                $expression = $this->CaseExpression();
2201 9
                break;
2202
2203
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
2204 181
            case ($this->isFunction()):
2205 101
                $this->lexer->peek(); // "("
2206
2207
                switch (true) {
2208 101
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
2209
                        // SUM(u.id) + COUNT(u.id)
2210 2
                        $expression = $this->ScalarExpression();
2211 2
                        break;
2212
2213
                    default:
2214
                        // IDENTITY(u)
2215 99
                        $expression = $this->FunctionDeclaration();
2216 99
                        break;
2217
                }
2218
2219 101
                break;
2220
2221
            // PartialObjectExpression (PARTIAL u.{id, name})
2222 81
            case ($lookaheadType === Lexer::T_PARTIAL):
2223 11
                $expression    = $this->PartialObjectExpression();
2224 11
                $identVariable = $expression->identificationVariable;
2225 11
                break;
2226
2227
            // Subselect
2228 70
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2229 23
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2230 23
                $expression = $this->Subselect();
2231 23
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2232 23
                break;
2233
2234
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2235 47
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2236 43
            case ($lookaheadType === Lexer::T_INTEGER):
2237 41
            case ($lookaheadType === Lexer::T_STRING):
2238 32
            case ($lookaheadType === Lexer::T_FLOAT):
2239
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2240 32
            case ($lookaheadType === Lexer::T_MINUS):
2241 32
            case ($lookaheadType === Lexer::T_PLUS):
2242 16
                $expression = $this->SimpleArithmeticExpression();
2243 16
                break;
2244
2245
            // NewObjectExpression (New ClassName(id, name))
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2246 31
            case ($lookaheadType === Lexer::T_NEW):
2247 28
                $expression = $this->NewObjectExpression();
2248 28
                break;
2249
2250
            default:
2251 3
                $this->syntaxError(
2252 3
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
2253 3
                    $this->lexer->lookahead
2254
                );
2255
        }
2256
2257
        // [["AS"] ["HIDDEN"] AliasResultVariable]
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
2258 790
        $mustHaveAliasResultVariable = false;
2259
2260 790
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2261 123
            $this->match(Lexer::T_AS);
2262
2263 123
            $mustHaveAliasResultVariable = true;
2264
        }
2265
2266 790
        $hiddenAliasResultVariable = false;
2267
2268 790
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2269 10
            $this->match(Lexer::T_HIDDEN);
2270
2271 10
            $hiddenAliasResultVariable = true;
2272
        }
2273
2274 790
        $aliasResultVariable = null;
2275
2276 790
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
0 ignored issues
show
introduced by
The condition $mustHaveAliasResultVariable is always true.
Loading history...
2277 132
            $token = $this->lexer->lookahead;
2278 132
            $aliasResultVariable = $this->AliasResultVariable();
2279
2280
            // Include AliasResultVariable in query components.
2281 127
            $this->queryComponents[$aliasResultVariable] = [
2282 127
                'resultVariable' => $expression,
2283 127
                'nestingLevel'   => $this->nestingLevel,
2284 127
                'token'          => $token,
2285
            ];
2286
        }
2287
2288
        // AST
2289
2290 785
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2291
2292 785
        if ($identVariable) {
2293 614
            $this->identVariableExpressions[$identVariable] = $expr;
2294
        }
2295
2296 785
        return $expr;
2297
    }
2298
2299
    /**
2300
     * SimpleSelectExpression ::= (
2301
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2302
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2303
     * ) [["AS"] AliasResultVariable]
2304
     *
2305
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2306
     */
2307 49
    public function SimpleSelectExpression()
2308
    {
2309 49
        $peek = $this->lexer->glimpse();
2310
2311 49
        switch ($this->lexer->lookahead['type']) {
2312 49
            case Lexer::T_IDENTIFIER:
2313
                switch (true) {
2314 19
                    case ($peek['type'] === Lexer::T_DOT):
2315 16
                        $expression = $this->StateFieldPathExpression();
2316
2317 16
                        return new AST\SimpleSelectExpression($expression);
2318
2319 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2320 2
                        $expression = $this->IdentificationVariable();
2321
2322 2
                        return new AST\SimpleSelectExpression($expression);
0 ignored issues
show
Bug introduced by
$expression of type string is incompatible with the type Doctrine\ORM\Query\AST\Node expected by parameter $expression of Doctrine\ORM\Query\AST\S...pression::__construct(). ( Ignorable by Annotation )

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

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