Completed
Push — 2.6 ( 3cfcd6...36e6a7 )
by Luís
25s queued 18s
created

Parser::WhenClause()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\ORM\Query;
21
22
use Doctrine\ORM\Mapping\ClassMetadata;
23
use Doctrine\ORM\Query;
24
use Doctrine\ORM\Query\AST\Functions;
25
use function 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 862
    public function __construct(Query $query)
188
    {
189 862
        $this->query        = $query;
190 862
        $this->em           = $query->getEntityManager();
191 862
        $this->lexer        = new Lexer($query->getDQL());
192 862
        $this->parserResult = new ParserResult();
193 862
    }
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 128
    public function setCustomOutputTreeWalker($className)
204
    {
205 128
        $this->customOutputWalker = $className;
0 ignored issues
show
Documentation Bug introduced by
It seems like $className of type string is incompatible with the declared type Doctrine\ORM\Query\TreeWalker of property $customOutputWalker.

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

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

Loading history...
206 128
    }
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 30
    public function getLexer()
226
    {
227 30
        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 862
    public function getAST()
258
    {
259
        // Parse & build AST
260 862
        $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 820
        $this->processDeferredIdentificationVariables();
265
266 818
        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 816
        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 604
            $this->processDeferredPathExpressions();
272
        }
273
274 813
        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 813
        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 809
        $this->processRootEntityAliasSelected();
283
284
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
285 808
        $this->fixIdentificationVariableOrder($AST);
286
287 808
        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 873
    public function match($token)
303
    {
304 873
        $lookaheadType = $this->lexer->lookahead['type'];
305
306
        // Short-circuit on first condition, usually types match
307 873
        if ($lookaheadType === $token) {
308 865
            $this->lexer->moveNext();
309 865
            return;
310
        }
311
312
        // If parameter is not identifier (1-99) must be exact match
313 21
        if ($token < Lexer::T_IDENTIFIER) {
314 3
            $this->syntaxError($this->lexer->getLiteral($token));
315
        }
316
317
        // If parameter is keyword (200+) must be exact match
318 18
        if ($token > Lexer::T_IDENTIFIER) {
319 7
            $this->syntaxError($this->lexer->getLiteral($token));
320
        }
321
322
        // If parameter is T_IDENTIFIER, then matches T_IDENTIFIER (100) and keywords (200+)
323 11
        if ($token === Lexer::T_IDENTIFIER && $lookaheadType < Lexer::T_IDENTIFIER) {
324 8
            $this->syntaxError($this->lexer->getLiteral($token));
325
        }
326
327 3
        $this->lexer->moveNext();
328 3
    }
329
330
    /**
331
     * Frees this parser, enabling it to be reused.
332
     *
333
     * @param boolean $deep     Whether to clean peek and reset errors.
334
     * @param integer $position Position to reset.
335
     *
336
     * @return void
337
     */
338
    public function free($deep = false, $position = 0)
339
    {
340
        // WARNING! Use this method with care. It resets the scanner!
341
        $this->lexer->resetPosition($position);
342
343
        // Deep = true cleans peek and also any previously defined errors
344
        if ($deep) {
345
            $this->lexer->resetPeek();
346
        }
347
348
        $this->lexer->token = null;
349
        $this->lexer->lookahead = null;
350
    }
351
352
    /**
353
     * Parses a query string.
354
     *
355
     * @return ParserResult
356
     */
357 862
    public function parse()
358
    {
359 862
        $AST = $this->getAST();
360
361 808
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
362 96
            $this->customTreeWalkers = $customWalkers;
363
        }
364
365 808
        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 808
        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 802
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
395 802
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
396
397
        // Assign an SQL executor to the parser result
398 802
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
399
400 794
        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 808
    private function fixIdentificationVariableOrder($AST)
415
    {
416 808
        if (count($this->identVariableExpressions) <= 1) {
417 631
            return;
418
        }
419
420 182
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
421 182
            if ( ! isset($this->identVariableExpressions[$dqlAlias])) {
422 8
                continue;
423
            }
424
425 182
            $expr = $this->identVariableExpressions[$dqlAlias];
426 182
            $key  = array_search($expr, $AST->selectClause->selectExpressions);
0 ignored issues
show
Bug introduced by
The property selectClause does not seem to exist on Doctrine\ORM\Query\AST\DeleteStatement.
Loading history...
Bug introduced by
The property selectClause does not seem to exist on Doctrine\ORM\Query\AST\UpdateStatement.
Loading history...
427
428 182
            unset($AST->selectClause->selectExpressions[$key]);
429
430 182
            $AST->selectClause->selectExpressions[] = $expr;
431
        }
432 182
    }
433
434
    /**
435
     * Generates a new syntax error.
436
     *
437
     * @param string     $expected Expected string.
438
     * @param array|null $token    Got token.
439
     *
440
     * @return void
441
     *
442
     * @throws \Doctrine\ORM\Query\QueryException
443
     */
444 18
    public function syntaxError($expected = '', $token = null)
445
    {
446 18
        if ($token === null) {
447 15
            $token = $this->lexer->lookahead;
448
        }
449
450 18
        $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
451
452 18
        $message  = "line 0, col {$tokenPos}: Error: ";
453 18
        $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected ';
454 18
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'";
455
456 18
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
457
    }
458
459
    /**
460
     * Generates a new semantical error.
461
     *
462
     * @param string     $message Optional message.
463
     * @param array|null $token   Optional token.
464
     *
465
     * @return void
466
     *
467
     * @throws \Doctrine\ORM\Query\QueryException
468
     */
469 35
    public function semanticalError($message = '', $token = null)
470
    {
471 35
        if ($token === null) {
472 2
            $token = $this->lexer->lookahead;
473
        }
474
475
        // Minimum exposed chars ahead of token
476 35
        $distance = 12;
477
478
        // Find a position of a final word to display in error string
479 35
        $dql    = $this->query->getDQL();
480 35
        $length = strlen($dql);
481 35
        $pos    = $token['position'] + $distance;
482 35
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
483 35
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
484
485 35
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
486 35
        $tokenStr = substr($dql, $token['position'], $length);
487
488
        // Building informative message
489 35
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
490
491 35
        throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL()));
492
    }
493
494
    /**
495
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
496
     *
497
     * @param boolean $resetPeek Reset peek after finding the closing parenthesis.
498
     *
499
     * @return array
500
     */
501 173
    private function peekBeyondClosingParenthesis($resetPeek = true)
502
    {
503 173
        $token = $this->lexer->peek();
504 173
        $numUnmatched = 1;
505
506 173
        while ($numUnmatched > 0 && $token !== null) {
507 172
            switch ($token['type']) {
508 172
                case Lexer::T_OPEN_PARENTHESIS:
509 47
                    ++$numUnmatched;
510 47
                    break;
511
512 172
                case Lexer::T_CLOSE_PARENTHESIS:
513 172
                    --$numUnmatched;
514 172
                    break;
515
516
                default:
517
                    // Do nothing
518
            }
519
520 172
            $token = $this->lexer->peek();
521
        }
522
523 173
        if ($resetPeek) {
524 152
            $this->lexer->resetPeek();
525
        }
526
527 173
        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 361
    private function isMathOperator($token)
538
    {
539 361
        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 407
    private function isFunction()
548
    {
549 407
        $lookaheadType = $this->lexer->lookahead['type'];
550 407
        $peek          = $this->lexer->peek();
551
552 407
        $this->lexer->resetPeek();
553
554 407
        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 4
    private function isAggregateFunction($tokenType)
565
    {
566 4
        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 305
    private function isNextAllAnySome()
575
    {
576 305
        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 820
    private function processDeferredIdentificationVariables()
586
    {
587 820
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
588 796
            $identVariable = $deferredItem['expression'];
589
590
            // Check if IdentificationVariable exists in queryComponents
591 796
            if ( ! isset($this->queryComponents[$identVariable])) {
592 1
                $this->semanticalError(
593 1
                    "'$identVariable' is not defined.", $deferredItem['token']
594
                );
595
            }
596
597 796
            $qComp = $this->queryComponents[$identVariable];
598
599
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
600 796
            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 796
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
608 1
                $this->semanticalError(
609 796
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
610
                );
611
            }
612
        }
613 818
    }
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 604
    private function processDeferredPathExpressions()
749
    {
750 604
        foreach ($this->deferredPathExpressions as $deferredItem) {
751 604
            $pathExpression = $deferredItem['expression'];
752
753 604
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
754 604
            $class = $qComp['metadata'];
755
756 604
            if (($field = $pathExpression->field) === null) {
757 39
                $field = $pathExpression->field = $class->identifier[0];
758
            }
759
760
            // Check if field or association exists
761 604
            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 603
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
769
770 603
            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 603
            $expectedType = $pathExpression->expectedType;
780
781 603
            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 601
            $pathExpression->type = $fieldType;
811
        }
812 601
    }
813
814
    /**
815
     * @return void
816
     */
817 809
    private function processRootEntityAliasSelected()
818
    {
819 809
        if ( ! count($this->identVariableExpressions)) {
820 240
            return;
821
        }
822
823 580
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
824 580
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
825 580
                return;
826
            }
827
        }
828
829 1
        $this->semanticalError('Cannot select entity through identification variables without choosing at least one root entity alias.');
830
    }
831
832
    /**
833
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
834
     *
835
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
836
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
837
     *         \Doctrine\ORM\Query\AST\DeleteStatement
838
     */
839 862
    public function QueryLanguage()
840
    {
841 862
        $statement = null;
842
843 862
        $this->lexer->moveNext();
844
845 862
        switch ($this->lexer->lookahead['type']) {
846 862
            case Lexer::T_SELECT:
847 796
                $statement = $this->SelectStatement();
848 758
                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 824
        if ($this->lexer->lookahead !== null) {
865 4
            $this->syntaxError('end of string');
866
        }
867
868 820
        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 796
    public function SelectStatement()
877
    {
878 796
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
879
880 762
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
881 759
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
882 758
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
883 758
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
884
885 758
        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 827
    public function IdentificationVariable()
922
    {
923 827
        $this->match(Lexer::T_IDENTIFIER);
924
925 827
        $identVariable = $this->lexer->token['value'];
926
927 827
        $this->deferredIdentificationVariables[] = [
928 827
            'expression'   => $identVariable,
929 827
            'nestingLevel' => $this->nestingLevel,
930 827
            'token'        => $this->lexer->token,
931
        ];
932
933 827
        return $identVariable;
934
    }
935
936
    /**
937
     * AliasIdentificationVariable = identifier
938
     *
939
     * @return string
940
     */
941 830
    public function AliasIdentificationVariable()
942
    {
943 830
        $this->match(Lexer::T_IDENTIFIER);
944
945 830
        $aliasIdentVariable = $this->lexer->token['value'];
946 830
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
947
948 830
        if ($exists) {
949 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
950
        }
951
952 830
        return $aliasIdentVariable;
953
    }
954
955
    /**
956
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
957
     *
958
     * @return string
959
     */
960 852
    public function AbstractSchemaName()
961
    {
962 852
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
963 834
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
964
965 834
            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 846
    private function validateAbstractSchemaName($schemaName)
989
    {
990 846
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
991 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
992
        }
993 831
    }
994
995
    /**
996
     * AliasResultVariable ::= identifier
997
     *
998
     * @return string
999
     */
1000 135
    public function AliasResultVariable()
1001
    {
1002 135
        $this->match(Lexer::T_IDENTIFIER);
1003
1004 131
        $resultVariable = $this->lexer->token['value'];
1005 131
        $exists = isset($this->queryComponents[$resultVariable]);
1006
1007 131
        if ($exists) {
1008 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1009
        }
1010
1011 131
        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 614
    public function PathExpression($expectedTypes)
1077
    {
1078 614
        $identVariable = $this->IdentificationVariable();
1079 614
        $field = null;
1080
1081 614
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1082 607
            $this->match(Lexer::T_DOT);
1083 607
            $this->match(Lexer::T_IDENTIFIER);
1084
1085 607
            $field = $this->lexer->token['value'];
1086
1087 607
            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 614
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1096
1097
        // Defer PathExpression validation if requested to be deferred
1098 614
        $this->deferredPathExpressions[] = [
1099 614
            'expression'   => $pathExpr,
1100 614
            'nestingLevel' => $this->nestingLevel,
1101 614
            'token'        => $this->lexer->token,
1102
        ];
1103
1104 614
        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 522
    public function SingleValuedPathExpression()
1126
    {
1127 522
        return $this->PathExpression(
1128 522
            AST\PathExpression::TYPE_STATE_FIELD |
1129 522
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1130
        );
1131
    }
1132
1133
    /**
1134
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1135
     *
1136
     * @return \Doctrine\ORM\Query\AST\PathExpression
1137
     */
1138 207
    public function StateFieldPathExpression()
1139
    {
1140 207
        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 796
    public function SelectClause()
1169
    {
1170 796
        $isDistinct = false;
1171 796
        $this->match(Lexer::T_SELECT);
1172
1173
        // Check for DISTINCT
1174 796
        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 796
        $selectExpressions = [];
1182 796
        $selectExpressions[] = $this->SelectExpression();
1183
1184 788
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1185 300
            $this->match(Lexer::T_COMMA);
1186
1187 300
            $selectExpressions[] = $this->SelectExpression();
1188
        }
1189
1190 787
        return new AST\SelectClause($selectExpressions, $isDistinct);
1191
    }
1192
1193
    /**
1194
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1195
     *
1196
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1197
     */
1198 49
    public function SimpleSelectClause()
1199
    {
1200 49
        $isDistinct = false;
1201 49
        $this->match(Lexer::T_SELECT);
1202
1203 49
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1204
            $this->match(Lexer::T_DISTINCT);
1205
1206
            $isDistinct = true;
1207
        }
1208
1209 49
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
1210
    }
1211
1212
    /**
1213
     * UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}*
1214
     *
1215
     * @return \Doctrine\ORM\Query\AST\UpdateClause
1216
     */
1217 32
    public function UpdateClause()
1218
    {
1219 32
        $this->match(Lexer::T_UPDATE);
1220
1221 32
        $token = $this->lexer->lookahead;
1222 32
        $abstractSchemaName = $this->AbstractSchemaName();
1223
1224 32
        $this->validateAbstractSchemaName($abstractSchemaName);
1225
1226 32
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1227 2
            $this->match(Lexer::T_AS);
1228
        }
1229
1230 32
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1231
1232 32
        $class = $this->em->getClassMetadata($abstractSchemaName);
1233
1234
        // Building queryComponent
1235
        $queryComponent = [
1236 32
            'metadata'     => $class,
1237
            'parent'       => null,
1238
            'relation'     => null,
1239
            'map'          => null,
1240 32
            'nestingLevel' => $this->nestingLevel,
1241 32
            'token'        => $token,
1242
        ];
1243
1244 32
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1245
1246 32
        $this->match(Lexer::T_SET);
1247
1248 32
        $updateItems = [];
1249 32
        $updateItems[] = $this->UpdateItem();
1250
1251 32
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1252 5
            $this->match(Lexer::T_COMMA);
1253
1254 5
            $updateItems[] = $this->UpdateItem();
1255
        }
1256
1257 32
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1258 32
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1259
1260 32
        return $updateClause;
1261
    }
1262
1263
    /**
1264
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1265
     *
1266
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1267
     */
1268 42
    public function DeleteClause()
1269
    {
1270 42
        $this->match(Lexer::T_DELETE);
1271
1272 42
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1273 9
            $this->match(Lexer::T_FROM);
1274
        }
1275
1276 42
        $token = $this->lexer->lookahead;
1277 42
        $abstractSchemaName = $this->AbstractSchemaName();
1278
1279 42
        $this->validateAbstractSchemaName($abstractSchemaName);
1280
1281 42
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1282
1283 42
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1284 1
            $this->match(Lexer::T_AS);
1285
        }
1286
1287 42
        $aliasIdentificationVariable = $this->lexer->isNextToken(Lexer::T_IDENTIFIER)
1288 40
            ? $this->AliasIdentificationVariable()
1289 42
            : 'alias_should_have_been_set';
1290
1291 42
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1292 42
        $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1293
1294
        // Building queryComponent
1295
        $queryComponent = [
1296 42
            'metadata'     => $class,
1297
            'parent'       => null,
1298
            'relation'     => null,
1299
            'map'          => null,
1300 42
            'nestingLevel' => $this->nestingLevel,
1301 42
            'token'        => $token,
1302
        ];
1303
1304 42
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1305
1306 42
        return $deleteClause;
1307
    }
1308
1309
    /**
1310
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1311
     *
1312
     * @return \Doctrine\ORM\Query\AST\FromClause
1313
     */
1314 787
    public function FromClause()
1315
    {
1316 787
        $this->match(Lexer::T_FROM);
1317
1318 782
        $identificationVariableDeclarations = [];
1319 782
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1320
1321 762
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1322 6
            $this->match(Lexer::T_COMMA);
1323
1324 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1325
        }
1326
1327 762
        return new AST\FromClause($identificationVariableDeclarations);
1328
    }
1329
1330
    /**
1331
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1332
     *
1333
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1334
     */
1335 49
    public function SubselectFromClause()
1336
    {
1337 49
        $this->match(Lexer::T_FROM);
1338
1339 49
        $identificationVariables = [];
1340 49
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1341
1342 48
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1343
            $this->match(Lexer::T_COMMA);
1344
1345
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1346
        }
1347
1348 48
        return new AST\SubselectFromClause($identificationVariables);
1349
    }
1350
1351
    /**
1352
     * WhereClause ::= "WHERE" ConditionalExpression
1353
     *
1354
     * @return \Doctrine\ORM\Query\AST\WhereClause
1355
     */
1356 347
    public function WhereClause()
1357
    {
1358 347
        $this->match(Lexer::T_WHERE);
1359
1360 347
        return new AST\WhereClause($this->ConditionalExpression());
1361
    }
1362
1363
    /**
1364
     * HavingClause ::= "HAVING" ConditionalExpression
1365
     *
1366
     * @return \Doctrine\ORM\Query\AST\HavingClause
1367
     */
1368 21
    public function HavingClause()
1369
    {
1370 21
        $this->match(Lexer::T_HAVING);
1371
1372 21
        return new AST\HavingClause($this->ConditionalExpression());
1373
    }
1374
1375
    /**
1376
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
1377
     *
1378
     * @return \Doctrine\ORM\Query\AST\GroupByClause
1379
     */
1380 36
    public function GroupByClause()
1381
    {
1382 36
        $this->match(Lexer::T_GROUP);
1383 36
        $this->match(Lexer::T_BY);
1384
1385 36
        $groupByItems = [$this->GroupByItem()];
1386
1387 35
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1388 8
            $this->match(Lexer::T_COMMA);
1389
1390 8
            $groupByItems[] = $this->GroupByItem();
1391
        }
1392
1393 35
        return new AST\GroupByClause($groupByItems);
1394
    }
1395
1396
    /**
1397
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1398
     *
1399
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1400
     */
1401 183
    public function OrderByClause()
1402
    {
1403 183
        $this->match(Lexer::T_ORDER);
1404 183
        $this->match(Lexer::T_BY);
1405
1406 183
        $orderByItems = [];
1407 183
        $orderByItems[] = $this->OrderByItem();
1408
1409 183
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1410 15
            $this->match(Lexer::T_COMMA);
1411
1412 15
            $orderByItems[] = $this->OrderByItem();
1413
        }
1414
1415 183
        return new AST\OrderByClause($orderByItems);
1416
    }
1417
1418
    /**
1419
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
1420
     *
1421
     * @return \Doctrine\ORM\Query\AST\Subselect
1422
     */
1423 49
    public function Subselect()
1424
    {
1425
        // Increase query nesting level
1426 49
        $this->nestingLevel++;
1427
1428 49
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1429
1430 48
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1431 48
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1432 48
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1433 48
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1434
1435
        // Decrease query nesting level
1436 48
        $this->nestingLevel--;
1437
1438 48
        return $subselect;
1439
    }
1440
1441
    /**
1442
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1443
     *
1444
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1445
     */
1446 32
    public function UpdateItem()
1447
    {
1448 32
        $pathExpr = $this->SingleValuedPathExpression();
1449
1450 32
        $this->match(Lexer::T_EQUALS);
1451
1452 32
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1453
1454 32
        return $updateItem;
1455
    }
1456
1457
    /**
1458
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1459
     *
1460
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1461
     */
1462 36
    public function GroupByItem()
1463
    {
1464
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1465 36
        $glimpse = $this->lexer->glimpse();
1466
1467 36
        if ($glimpse['type'] === Lexer::T_DOT) {
1468 16
            return $this->SingleValuedPathExpression();
1469
        }
1470
1471
        // Still need to decide between IdentificationVariable or ResultVariable
1472 20
        $lookaheadValue = $this->lexer->lookahead['value'];
1473
1474 20
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1475 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1476
        }
1477
1478 19
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1479 17
            ? $this->IdentificationVariable()
1480 19
            : $this->ResultVariable();
1481
    }
1482
1483
    /**
1484
     * OrderByItem ::= (
1485
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1486
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1487
     * ) ["ASC" | "DESC"]
1488
     *
1489
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1490
     */
1491 183
    public function OrderByItem()
1492
    {
1493 183
        $this->lexer->peek(); // lookahead => '.'
1494 183
        $this->lexer->peek(); // lookahead => token after '.'
1495
1496 183
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1497
1498 183
        $this->lexer->resetPeek();
1499
1500 183
        $glimpse = $this->lexer->glimpse();
1501
1502
        switch (true) {
1503 183
            case ($this->isFunction()):
1504 2
                $expr = $this->FunctionDeclaration();
1505 2
                break;
1506
1507 181
            case ($this->isMathOperator($peek)):
1508 25
                $expr = $this->SimpleArithmeticExpression();
1509 25
                break;
1510
1511 157
            case ($glimpse['type'] === Lexer::T_DOT):
1512 142
                $expr = $this->SingleValuedPathExpression();
1513 142
                break;
1514
1515 19
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1516 2
                $expr = $this->ScalarExpression();
1517 2
                break;
1518
1519
            default:
1520 17
                $expr = $this->ResultVariable();
1521 17
                break;
1522
        }
1523
1524 183
        $type = 'ASC';
1525 183
        $item = new AST\OrderByItem($expr);
1526
1527
        switch (true) {
1528 183
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1529 95
                $this->match(Lexer::T_DESC);
1530 95
                $type = 'DESC';
1531 95
                break;
1532
1533 155
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1534 97
                $this->match(Lexer::T_ASC);
1535 97
                break;
1536
1537
            default:
1538
                // Do nothing
1539
        }
1540
1541 183
        $item->type = $type;
1542
1543 183
        return $item;
1544
    }
1545
1546
    /**
1547
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1548
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1549
     *
1550
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1551
     * grammar that needs to be supported:
1552
     *
1553
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1554
     *
1555
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1556
     *
1557
     * @return AST\ArithmeticExpression
1558
     */
1559 32
    public function NewValue()
1560
    {
1561 32
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1562 1
            $this->match(Lexer::T_NULL);
1563
1564 1
            return null;
1565
        }
1566
1567 31
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1568 19
            $this->match(Lexer::T_INPUT_PARAMETER);
1569
1570 19
            return new AST\InputParameter($this->lexer->token['value']);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Doctrine\ORM\...>lexer->token['value']) returns the type Doctrine\ORM\Query\AST\InputParameter which is incompatible with the documented return type Doctrine\ORM\Query\AST\ArithmeticExpression.
Loading history...
1571
        }
1572
1573 12
        return $this->ArithmeticExpression();
1574
    }
1575
1576
    /**
1577
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1578
     *
1579
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1580
     */
1581 784
    public function IdentificationVariableDeclaration()
1582
    {
1583 784
        $joins                    = [];
1584 784
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1585 767
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1586 8
            ? $this->IndexBy()
1587 767
            : null;
1588
1589 767
        $rangeVariableDeclaration->isRoot = true;
1590
1591
        while (
1592 767
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1593 767
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1594 767
            $this->lexer->isNextToken(Lexer::T_JOIN)
1595
        ) {
1596 281
            $joins[] = $this->Join();
1597
        }
1598
1599 764
        return new AST\IdentificationVariableDeclaration(
1600 764
            $rangeVariableDeclaration, $indexBy, $joins
1601
        );
1602
    }
1603
1604
    /**
1605
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1606
     *
1607
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1608
     * Desired EBNF support:
1609
     *
1610
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1611
     *
1612
     * It demands that entire SQL generation to become programmatical. This is
1613
     * needed because association based subselect requires "WHERE" conditional
1614
     * expressions to be injected, but there is no scope to do that. Only scope
1615
     * accessible is "FROM", prohibiting an easy implementation without larger
1616
     * changes.}
1617
     *
1618
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1619
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1620
     */
1621 49
    public function SubselectIdentificationVariableDeclaration()
1622
    {
1623
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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