Completed
Pull Request — 2.6 (#7077)
by Luís
15:44 queued 08:30
created

Parser::InstanceOfParameter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
2264 788
        $mustHaveAliasResultVariable = false;
2265
2266 788
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2267 123
            $this->match(Lexer::T_AS);
2268
2269 123
            $mustHaveAliasResultVariable = true;
2270
        }
2271
2272 788
        $hiddenAliasResultVariable = false;
2273
2274 788
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2275 10
            $this->match(Lexer::T_HIDDEN);
2276
2277 10
            $hiddenAliasResultVariable = true;
2278
        }
2279
2280 788
        $aliasResultVariable = null;
2281
2282 788
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2283 132
            $token = $this->lexer->lookahead;
2284 132
            $aliasResultVariable = $this->AliasResultVariable();
2285
2286
            // Include AliasResultVariable in query components.
2287 127
            $this->queryComponents[$aliasResultVariable] = [
2288 127
                'resultVariable' => $expression,
2289 127
                'nestingLevel'   => $this->nestingLevel,
2290 127
                'token'          => $token,
2291
            ];
2292
        }
2293
2294
        // AST
2295
2296 783
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2297
2298 783
        if ($identVariable) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $identVariable of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
2299 612
            $this->identVariableExpressions[$identVariable] = $expr;
2300
        }
2301
2302 783
        return $expr;
2303
    }
2304
2305
    /**
2306
     * SimpleSelectExpression ::= (
2307
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
2308
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
2309
     * ) [["AS"] AliasResultVariable]
2310
     *
2311
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
2312
     */
2313 49
    public function SimpleSelectExpression()
2314
    {
2315 49
        $peek = $this->lexer->glimpse();
2316
2317 49
        switch ($this->lexer->lookahead['type']) {
2318 49
            case Lexer::T_IDENTIFIER:
2319
                switch (true) {
2320 19
                    case ($peek['type'] === Lexer::T_DOT):
2321 16
                        $expression = $this->StateFieldPathExpression();
2322
2323 16
                        return new AST\SimpleSelectExpression($expression);
2324
2325 3
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2326 2
                        $expression = $this->IdentificationVariable();
2327
2328 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

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

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