Passed
Pull Request — 2.7 (#7934)
by Benjamin
08:02
created

Parser::match()   B

Complexity

Conditions 7
Paths 18

Size

Total Lines 26
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 7

Importance

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

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

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

Loading history...
207 128
    }
208
209
    /**
210
     * Adds a custom tree walker for modifying the AST.
211
     *
212
     * @param string $className
213
     *
214
     * @return void
215
     */
216
    public function addCustomTreeWalker($className)
217
    {
218
        $this->customTreeWalkers[] = $className;
219
    }
220
221
    /**
222
     * Gets the lexer used by the parser.
223
     *
224
     * @return \Doctrine\ORM\Query\Lexer
225
     */
226 30
    public function getLexer()
227
    {
228 30
        return $this->lexer;
229
    }
230
231
    /**
232
     * Gets the ParserResult that is being filled with information during parsing.
233
     *
234
     * @return \Doctrine\ORM\Query\ParserResult
235
     */
236
    public function getParserResult()
237
    {
238
        return $this->parserResult;
239
    }
240
241
    /**
242
     * Gets the EntityManager used by the parser.
243
     *
244
     * @return \Doctrine\ORM\EntityManager
245
     */
246
    public function getEntityManager()
247
    {
248
        return $this->em;
249
    }
250
251
    /**
252
     * Parses and builds AST for the given Query.
253
     *
254
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
255
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
256
     *         \Doctrine\ORM\Query\AST\DeleteStatement
257
     */
258 859
    public function getAST()
259
    {
260
        // Parse & build AST
261 859
        $AST = $this->QueryLanguage();
262
263
        // Process any deferred validations of some nodes in the AST.
264
        // This also allows post-processing of the AST for modification purposes.
265 817
        $this->processDeferredIdentificationVariables();
266
267 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...
268 11
            $this->processDeferredPartialObjectExpressions();
269
        }
270
271 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...
272 596
            $this->processDeferredPathExpressions();
273
        }
274
275 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...
276 30
            $this->processDeferredResultVariables();
277
        }
278
279 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...
280 28
            $this->processDeferredNewObjectExpressions($AST);
0 ignored issues
show
Bug introduced by
$AST of type Doctrine\ORM\Query\AST\SelectStatement is incompatible with the type Doctrine\ORM\Query\AST\SelectClause expected by parameter $AST of Doctrine\ORM\Query\Parse...dNewObjectExpressions(). ( Ignorable by Annotation )

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

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

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

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

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

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

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

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