Failed Conditions
Pull Request — 2.6 (#7882)
by
unknown
08:05
created

Parser::HavingClause()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 5
ccs 3
cts 3
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 in_array;
26
use function strpos;
27
28
/**
29
 * An LL(*) recursive-descent parser for the context-free grammar of the Doctrine Query Language.
30
 * Parses a DQL query, reports any errors in it, and generates an AST.
31
 *
32
 * @since   2.0
33
 * @author  Guilherme Blanco <[email protected]>
34
 * @author  Jonathan Wage <[email protected]>
35
 * @author  Roman Borschel <[email protected]>
36
 * @author  Janne Vanhala <[email protected]>
37
 * @author  Fabio B. Silva <[email protected]>
38
 */
39
class Parser
40
{
41
    /**
42
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
43
     *
44
     * @var array
45
     */
46
    private static $_STRING_FUNCTIONS = [
47
        'concat'    => Functions\ConcatFunction::class,
48
        'substring' => Functions\SubstringFunction::class,
49
        'trim'      => Functions\TrimFunction::class,
50
        'lower'     => Functions\LowerFunction::class,
51
        'upper'     => Functions\UpperFunction::class,
52
        'identity'  => Functions\IdentityFunction::class,
53
    ];
54
55
    /**
56
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
57
     *
58
     * @var array
59
     */
60
    private static $_NUMERIC_FUNCTIONS = [
61
        'length'    => Functions\LengthFunction::class,
62
        'locate'    => Functions\LocateFunction::class,
63
        'abs'       => Functions\AbsFunction::class,
64
        'sqrt'      => Functions\SqrtFunction::class,
65
        'mod'       => Functions\ModFunction::class,
66
        'size'      => Functions\SizeFunction::class,
67
        'date_diff' => Functions\DateDiffFunction::class,
68
        'bit_and'   => Functions\BitAndFunction::class,
69
        'bit_or'    => Functions\BitOrFunction::class,
70
71
        // Aggregate functions
72
        'min'       => Functions\MinFunction::class,
73
        'max'       => Functions\MaxFunction::class,
74
        'avg'       => Functions\AvgFunction::class,
75
        'sum'       => Functions\SumFunction::class,
76
        'count'     => Functions\CountFunction::class,
77
    ];
78
79
    /**
80
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
81
     *
82
     * @var array
83
     */
84
    private static $_DATETIME_FUNCTIONS = [
85
        'current_date'      => Functions\CurrentDateFunction::class,
86
        'current_time'      => Functions\CurrentTimeFunction::class,
87
        'current_timestamp' => Functions\CurrentTimestampFunction::class,
88
        'date_add'          => Functions\DateAddFunction::class,
89
        'date_sub'          => Functions\DateSubFunction::class,
90
    ];
91
92
    /*
93
     * Expressions that were encountered during parsing of identifiers and expressions
94
     * and still need to be validated.
95
     */
96
97
    /**
98
     * @var array
99
     */
100
    private $deferredIdentificationVariables = [];
101
102
    /**
103
     * @var array
104
     */
105
    private $deferredPartialObjectExpressions = [];
106
107
    /**
108
     * @var array
109
     */
110
    private $deferredPathExpressions = [];
111
112
    /**
113
     * @var array
114
     */
115
    private $deferredResultVariables = [];
116
117
    /**
118
     * @var array
119
     */
120
    private $deferredNewObjectExpressions = [];
121
122
    /**
123
     * The lexer.
124
     *
125
     * @var \Doctrine\ORM\Query\Lexer
126
     */
127
    private $lexer;
128
129
    /**
130
     * The parser result.
131
     *
132
     * @var \Doctrine\ORM\Query\ParserResult
133
     */
134
    private $parserResult;
135
136
    /**
137
     * The EntityManager.
138
     *
139
     * @var \Doctrine\ORM\EntityManager
140
     */
141
    private $em;
142
143
    /**
144
     * The Query to parse.
145
     *
146
     * @var Query
147
     */
148
    private $query;
149
150
    /**
151
     * Map of declared query components in the parsed query.
152
     *
153
     * @var array
154
     */
155
    private $queryComponents = [];
156
157
    /**
158
     * Keeps the nesting level of defined ResultVariables.
159
     *
160
     * @var integer
161
     */
162
    private $nestingLevel = 0;
163
164
    /**
165
     * Any additional custom tree walkers that modify the AST.
166
     *
167
     * @var array
168
     */
169
    private $customTreeWalkers = [];
170
171
    /**
172
     * The custom last tree walker, if any, that is responsible for producing the output.
173
     *
174
     * @var TreeWalker
175
     */
176
    private $customOutputWalker;
177
178
    /**
179
     * @var array
180
     */
181
    private $identVariableExpressions = [];
182
183
    /**
184
     * Creates a new query parser object.
185
     *
186
     * @param Query $query The Query to parse.
187
     */
188 846
    public function __construct(Query $query)
189
    {
190 846
        $this->query        = $query;
191 846
        $this->em           = $query->getEntityManager();
192 846
        $this->lexer        = new Lexer($query->getDQL());
193 846
        $this->parserResult = new ParserResult();
194 846
    }
195
196
    /**
197
     * Sets a custom tree walker that produces output.
198
     * This tree walker will be run last over the AST, after any other walkers.
199
     *
200
     * @param string $className
201
     *
202
     * @return void
203
     */
204 128
    public function setCustomOutputTreeWalker($className)
205
    {
206 128
        $this->customOutputWalker = $className;
0 ignored issues
show
Documentation Bug introduced by
It seems like $className of type string is incompatible with the declared type Doctrine\ORM\Query\TreeWalker of property $customOutputWalker.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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