Failed Conditions
Pull Request — 2.6 (#7882)
by
unknown
06:45
created

Parser::JoinAssociationDeclaration()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 30
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 3.0017

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 30
ccs 16
cts 17
cp 0.9412
rs 9.6666
cc 3
nc 4
nop 0
crap 3.0017
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 524
    public function __construct(Query $query)
189
    {
190 524
        $this->query        = $query;
191 524
        $this->em           = $query->getEntityManager();
192 524
        $this->lexer        = new Lexer($query->getDQL());
193 524
        $this->parserResult = new ParserResult();
194 524
    }
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 23
    public function getLexer()
227
    {
228 23
        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 524
    public function getAST()
259
    {
260
        // Parse & build AST
261 524
        $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 482
        $this->processDeferredIdentificationVariables();
266
267 480
        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 8
            $this->processDeferredPartialObjectExpressions();
269
        }
270
271 478
        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 360
            $this->processDeferredPathExpressions();
273
        }
274
275 475
        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 27
            $this->processDeferredResultVariables();
277
        }
278
279 475
        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 3
            $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 475
        $this->processRootEntityAliasSelected();
284
285
        // TODO: Is there a way to remove this? It may impact the mixed hydration resultset a lot!
286 474
        $this->fixIdentificationVariableOrder($AST);
287
288 474
        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 535
    public function match($token)
304
    {
305 535
        $lookaheadType = $this->lexer->lookahead['type'];
306
307
        // Short-circuit on first condition, usually types match
308 535
        if ($lookaheadType === $token) {
309 527
            $this->lexer->moveNext();
310 527
            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 524
    public function parse()
359
    {
360 524
        $AST = $this->getAST();
361
362 474
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
363 39
            $this->customTreeWalkers = $customWalkers;
364
        }
365
366 474
        if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
367 32
            $this->customOutputWalker = $customOutputWalker;
368
        }
369
370
        // Run any custom tree walkers over the AST
371 474
        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 38
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
373
374 38
            foreach ($this->customTreeWalkers as $walker) {
375 38
                $treeWalkerChain->addTreeWalker($walker);
376
            }
377
378
            switch (true) {
379 38
                case ($AST instanceof AST\UpdateStatement):
380
                    $treeWalkerChain->walkUpdateStatement($AST);
381
                    break;
382
383 38
                case ($AST instanceof AST\DeleteStatement):
384
                    $treeWalkerChain->walkDeleteStatement($AST);
385
                    break;
386
387 38
                case ($AST instanceof AST\SelectStatement):
388
                default:
389 38
                    $treeWalkerChain->walkSelectStatement($AST);
390
            }
391
392 37
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
393
        }
394
395 473
        $outputWalkerClass = $this->customOutputWalker ?: SqlWalker::class;
396 473
        $outputWalker      = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
397
398
        // Assign an SQL executor to the parser result
399 473
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
400
401 466
        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 474
    private function fixIdentificationVariableOrder($AST)
416
    {
417 474
        if (count($this->identVariableExpressions) <= 1) {
418 416
            return;
419
        }
420
421 58
        foreach ($this->queryComponents as $dqlAlias => $qComp) {
422 58
            if ( ! isset($this->identVariableExpressions[$dqlAlias])) {
423 7
                continue;
424
            }
425
426 58
            $expr = $this->identVariableExpressions[$dqlAlias];
427 58
            $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 58
            unset($AST->selectClause->selectExpressions[$key]);
430
431 58
            $AST->selectClause->selectExpressions[] = $expr;
432
        }
433 58
    }
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 31
    public function semanticalError($message = '', $token = null)
471
    {
472 31
        if ($token === null) {
473 2
            $token = $this->lexer->lookahead ?? ['position' => null];
474
        }
475
476
        // Minimum exposed chars ahead of token
477 31
        $distance = 12;
478
479
        // Find a position of a final word to display in error string
480 31
        $dql    = $this->query->getDQL();
481 31
        $length = strlen($dql);
482 31
        $pos    = $token['position'] + $distance;
483 31
        $pos    = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
484 31
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
485
486 31
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
487 31
        $tokenStr = substr($dql, $token['position'], $length);
488
489
        // Building informative message
490 31
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
491
492 31
        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 113
    private function peekBeyondClosingParenthesis($resetPeek = true)
503
    {
504 113
        $token = $this->lexer->peek();
505 113
        $numUnmatched = 1;
506
507 113
        while ($numUnmatched > 0 && $token !== null) {
508 112
            switch ($token['type']) {
509 112
                case Lexer::T_OPEN_PARENTHESIS:
510 25
                    ++$numUnmatched;
511 25
                    break;
512
513 112
                case Lexer::T_CLOSE_PARENTHESIS:
514 112
                    --$numUnmatched;
515 112
                    break;
516
517
                default:
518
                    // Do nothing
519
            }
520
521 112
            $token = $this->lexer->peek();
522
        }
523
524 113
        if ($resetPeek) {
525 93
            $this->lexer->resetPeek();
526
        }
527
528 113
        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 185
    private function isMathOperator($token)
539
    {
540 185
        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 227
    private function isFunction()
549
    {
550 227
        $lookaheadType = $this->lexer->lookahead['type'];
551 227
        $peek          = $this->lexer->peek();
552
553 227
        $this->lexer->resetPeek();
554
555 227
        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 203
    private function isNextAllAnySome()
576
    {
577 203
        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 482
    private function processDeferredIdentificationVariables()
587
    {
588 482
        foreach ($this->deferredIdentificationVariables as $deferredItem) {
589 475
            $identVariable = $deferredItem['expression'];
590
591
            // Check if IdentificationVariable exists in queryComponents
592 475
            if ( ! isset($this->queryComponents[$identVariable])) {
593 1
                $this->semanticalError(
594 1
                    "'$identVariable' is not defined.", $deferredItem['token']
595
                );
596
            }
597
598 475
            $qComp = $this->queryComponents[$identVariable];
599
600
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
601 475
            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 475
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
609 1
                $this->semanticalError(
610 475
                    "'$identVariable' is used outside the scope of its declaration.", $deferredItem['token']
611
                );
612
            }
613
        }
614 480
    }
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 3
    private function processDeferredNewObjectExpressions($AST)
624
    {
625 3
        foreach ($this->deferredNewObjectExpressions as $deferredItem) {
626 3
            $expression     = $deferredItem['expression'];
627 3
            $token          = $deferredItem['token'];
628 3
            $className      = $expression->className;
629 3
            $args           = $expression->args;
630 3
            $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 3
                ? $AST->fromClause->identificationVariableDeclarations[0]->rangeVariableDeclaration->abstractSchemaName
632 3
                : null;
633
634
            // If the namespace is not given then assumes the first FROM entity namespace
635 3
            if (strpos($className, '\\') === false && ! class_exists($className) && strpos($fromClassName, '\\') !== false) {
636
                $namespace  = substr($fromClassName, 0, strrpos($fromClassName, '\\'));
637
                $fqcn       = $namespace . '\\' . $className;
638
639
                if (class_exists($fqcn)) {
640
                    $expression->className  = $fqcn;
641
                    $className              = $fqcn;
642
                }
643
            }
644
645 3
            if ( ! class_exists($className)) {
646
                $this->semanticalError(sprintf('Class "%s" is not defined.', $className), $token);
647
            }
648
649 3
            $class = new \ReflectionClass($className);
650
651 3
            if ( ! $class->isInstantiable()) {
652
                $this->semanticalError(sprintf('Class "%s" can not be instantiated.', $className), $token);
653
            }
654
655 3
            if ($class->getConstructor() === null) {
656
                $this->semanticalError(sprintf('Class "%s" has not a valid constructor.', $className), $token);
657
            }
658
659 3
            if ($class->getConstructor()->getNumberOfRequiredParameters() > count($args)) {
660 3
                $this->semanticalError(sprintf('Number of arguments does not match with "%s" constructor declaration.', $className), $token);
661
            }
662
        }
663 3
    }
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 8
    private function processDeferredPartialObjectExpressions()
672
    {
673 8
        foreach ($this->deferredPartialObjectExpressions as $deferredItem) {
674 8
            $expr = $deferredItem['expression'];
675 8
            $class = $this->queryComponents[$expr->identificationVariable]['metadata'];
676
677 8
            foreach ($expr->partialFieldSet as $field) {
678 8
                if (isset($class->fieldMappings[$field])) {
679 8
                    continue;
680
                }
681
682 2
                if (isset($class->associationMappings[$field]) &&
683 2
                    $class->associationMappings[$field]['isOwningSide'] &&
684 2
                    $class->associationMappings[$field]['type'] & ClassMetadata::TO_ONE) {
685 1
                    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 7
            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 7
                    $deferredItem['token']
697
                );
698
            }
699
        }
700 6
    }
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 27
    private function processDeferredResultVariables()
709
    {
710 27
        foreach ($this->deferredResultVariables as $deferredItem) {
711 27
            $resultVariable = $deferredItem['expression'];
712
713
            // Check if ResultVariable exists in queryComponents
714 27
            if ( ! isset($this->queryComponents[$resultVariable])) {
715
                $this->semanticalError(
716
                    "'$resultVariable' is not defined.", $deferredItem['token']
717
                );
718
            }
719
720 27
            $qComp = $this->queryComponents[$resultVariable];
721
722
            // Check if queryComponent points to an AbstractSchemaName or a ResultVariable
723 27
            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 27
            if ($qComp['nestingLevel'] > $deferredItem['nestingLevel']) {
731
                $this->semanticalError(
732 27
                    "'$resultVariable' is used outside the scope of its declaration.", $deferredItem['token']
733
                );
734
            }
735
        }
736 27
    }
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 360
    private function processDeferredPathExpressions()
750
    {
751 360
        foreach ($this->deferredPathExpressions as $deferredItem) {
752 360
            $pathExpression = $deferredItem['expression'];
753
754 360
            $qComp = $this->queryComponents[$pathExpression->identificationVariable];
755 360
            $class = $qComp['metadata'];
756
757 360
            if (($field = $pathExpression->field) === null) {
758 23
                $field = $pathExpression->field = $class->identifier[0];
759
            }
760
761
            // Check if field or association exists
762 360
            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 359
            $fieldType = AST\PathExpression::TYPE_STATE_FIELD;
770
771 359
            if (isset($class->associationMappings[$field])) {
772 67
                $assoc = $class->associationMappings[$field];
773
774 67
                $fieldType = ($assoc['type'] & ClassMetadata::TO_ONE)
775 46
                    ? AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
776 67
                    : AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION;
777
            }
778
779
            // Validate if PathExpression is one of the expected types
780 359
            $expectedType = $pathExpression->expectedType;
781
782 359
            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 357
            $pathExpression->type = $fieldType;
812
        }
813 357
    }
814
815
    /**
816
     * @return void
817
     */
818 475
    private function processRootEntityAliasSelected()
819
    {
820 475
        if ( ! count($this->identVariableExpressions)) {
821 174
            return;
822
        }
823
824 309
        foreach ($this->identVariableExpressions as $dqlAlias => $expr) {
825 309
            if (isset($this->queryComponents[$dqlAlias]) && $this->queryComponents[$dqlAlias]['parent'] === null) {
826 309
                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 524
    public function QueryLanguage()
841
    {
842 524
        $statement = null;
843
844 524
        $this->lexer->moveNext();
845
846 524
        switch ($this->lexer->lookahead['type'] ?? null) {
847 524
            case Lexer::T_SELECT:
848 465
                $statement = $this->SelectStatement();
849 427
                break;
850
851 63
            case Lexer::T_UPDATE:
852 28
                $statement = $this->UpdateStatement();
853 28
                break;
854
855 36
            case Lexer::T_DELETE:
856 35
                $statement = $this->DeleteStatement();
857 35
                break;
858
859
            default:
860 2
                $this->syntaxError('SELECT, UPDATE or DELETE');
861
                break;
862
        }
863
864
        // Check for end of string
865 486
        if ($this->lexer->lookahead !== null) {
866 4
            $this->syntaxError('end of string');
867
        }
868
869 482
        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 465
    public function SelectStatement()
878
    {
879 465
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
880
881 431
        $selectStatement->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
882 428
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
883 427
        $selectStatement->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
884 427
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
885
886 427
        return $selectStatement;
887
    }
888
889
    /**
890
     * UpdateStatement ::= UpdateClause [WhereClause]
891
     *
892
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
893
     */
894 28
    public function UpdateStatement()
895
    {
896 28
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
897
898 28
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
899
900 28
        return $updateStatement;
901
    }
902
903
    /**
904
     * DeleteStatement ::= DeleteClause [WhereClause]
905
     *
906
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
907
     */
908 35
    public function DeleteStatement()
909
    {
910 35
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
911
912 35
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
913
914 35
        return $deleteStatement;
915
    }
916
917
    /**
918
     * IdentificationVariable ::= identifier
919
     *
920
     * @return string
921
     */
922 506
    public function IdentificationVariable()
923
    {
924 506
        $this->match(Lexer::T_IDENTIFIER);
925
926 506
        $identVariable = $this->lexer->token['value'];
927
928 506
        $this->deferredIdentificationVariables[] = [
929 506
            'expression'   => $identVariable,
930 506
            'nestingLevel' => $this->nestingLevel,
931 506
            'token'        => $this->lexer->token,
932
        ];
933
934 506
        return $identVariable;
935
    }
936
937
    /**
938
     * AliasIdentificationVariable = identifier
939
     *
940
     * @return string
941
     */
942 492
    public function AliasIdentificationVariable()
943
    {
944 492
        $this->match(Lexer::T_IDENTIFIER);
945
946 492
        $aliasIdentVariable = $this->lexer->token['value'];
947 492
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
948
949 492
        if ($exists) {
950 2
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
951
        }
952
953 492
        return $aliasIdentVariable;
954
    }
955
956
    /**
957
     * AbstractSchemaName ::= fully_qualified_name | aliased_name | identifier
958
     *
959
     * @return string
960
     */
961 514
    public function AbstractSchemaName()
962
    {
963 514
        if ($this->lexer->isNextToken(Lexer::T_FULLY_QUALIFIED_NAME)) {
964 498
            $this->match(Lexer::T_FULLY_QUALIFIED_NAME);
965
966 498
            return $this->lexer->token['value'];
967
        }
968
969 17
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
970 8
            $this->match(Lexer::T_IDENTIFIER);
971
972 8
            return $this->lexer->token['value'];
973
        }
974
975 9
        $this->match(Lexer::T_ALIASED_NAME);
976
977 8
        [$namespaceAlias, $simpleClassName] = explode(':', $this->lexer->token['value']);
978
979 8
        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 508
    private function validateAbstractSchemaName($schemaName)
990
    {
991 508
        if (! (class_exists($schemaName, true) || interface_exists($schemaName, true))) {
992 16
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
993
        }
994 493
    }
995
996
    /**
997
     * AliasResultVariable ::= identifier
998
     *
999
     * @return string
1000
     */
1001 77
    public function AliasResultVariable()
1002
    {
1003 77
        $this->match(Lexer::T_IDENTIFIER);
1004
1005 73
        $resultVariable = $this->lexer->token['value'];
1006 73
        $exists = isset($this->queryComponents[$resultVariable]);
1007
1008 73
        if ($exists) {
1009 2
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
1010
        }
1011
1012 73
        return $resultVariable;
1013
    }
1014
1015
    /**
1016
     * ResultVariable ::= identifier
1017
     *
1018
     * @return string
1019
     */
1020 27
    public function ResultVariable()
1021
    {
1022 27
        $this->match(Lexer::T_IDENTIFIER);
1023
1024 27
        $resultVariable = $this->lexer->token['value'];
1025
1026
        // Defer ResultVariable validation
1027 27
        $this->deferredResultVariables[] = [
1028 27
            'expression'   => $resultVariable,
1029 27
            'nestingLevel' => $this->nestingLevel,
1030 27
            'token'        => $this->lexer->token,
1031
        ];
1032
1033 27
        return $resultVariable;
1034
    }
1035
1036
    /**
1037
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
1038
     *
1039
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
1040
     */
1041 93
    public function JoinAssociationPathExpression()
1042
    {
1043 93
        $identVariable = $this->IdentificationVariable();
1044
1045 93
        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 93
        $this->match(Lexer::T_DOT);
1052 93
        $this->match(Lexer::T_IDENTIFIER);
1053
1054 93
        $field = $this->lexer->token['value'];
1055
1056
        // Validate association field
1057 93
        $qComp = $this->queryComponents[$identVariable];
1058 93
        $class = $qComp['metadata'];
1059
1060 93
        if ( ! $class->hasAssociation($field)) {
1061
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
1062
        }
1063
1064 93
        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 370
    public function PathExpression($expectedTypes)
1078
    {
1079 370
        $identVariable = $this->IdentificationVariable();
1080 370
        $field = null;
1081
1082 370
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
1083 369
            $this->match(Lexer::T_DOT);
1084 369
            $this->match(Lexer::T_IDENTIFIER);
1085
1086 369
            $field = $this->lexer->token['value'];
1087
1088 369
            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 370
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
1097
1098
        // Defer PathExpression validation if requested to be deferred
1099 370
        $this->deferredPathExpressions[] = [
1100 370
            'expression'   => $pathExpr,
1101 370
            'nestingLevel' => $this->nestingLevel,
1102 370
            'token'        => $this->lexer->token,
1103
        ];
1104
1105 370
        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 290
    public function SingleValuedPathExpression()
1127
    {
1128 290
        return $this->PathExpression(
1129 290
            AST\PathExpression::TYPE_STATE_FIELD |
1130 290
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
1131
        );
1132
    }
1133
1134
    /**
1135
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
1136
     *
1137
     * @return \Doctrine\ORM\Query\AST\PathExpression
1138
     */
1139 145
    public function StateFieldPathExpression()
1140
    {
1141 145
        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 8
    public function SingleValuedAssociationPathExpression()
1150
    {
1151 8
        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 465
    public function SelectClause()
1170
    {
1171 465
        $isDistinct = false;
1172 465
        $this->match(Lexer::T_SELECT);
1173
1174
        // Check for DISTINCT
1175 465
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1176 5
            $this->match(Lexer::T_DISTINCT);
1177
1178 5
            $isDistinct = true;
1179
        }
1180
1181
        // Process SelectExpressions (1..N)
1182 465
        $selectExpressions = [];
1183 465
        $selectExpressions[] = $this->SelectExpression();
1184
1185 457
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1186 121
            $this->match(Lexer::T_COMMA);
1187
1188 121
            $selectExpressions[] = $this->SelectExpression();
1189
        }
1190
1191 456
        return new AST\SelectClause($selectExpressions, $isDistinct);
1192
    }
1193
1194
    /**
1195
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
1196
     *
1197
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
1198
     */
1199 42
    public function SimpleSelectClause()
1200
    {
1201 42
        $isDistinct = false;
1202 42
        $this->match(Lexer::T_SELECT);
1203
1204 42
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
1205
            $this->match(Lexer::T_DISTINCT);
1206
1207
            $isDistinct = true;
1208
        }
1209
1210 42
        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 28
    public function UpdateClause()
1219
    {
1220 28
        $this->match(Lexer::T_UPDATE);
1221
1222 28
        $token = $this->lexer->lookahead;
1223 28
        $abstractSchemaName = $this->AbstractSchemaName();
1224
1225 28
        $this->validateAbstractSchemaName($abstractSchemaName);
1226
1227 28
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1228 2
            $this->match(Lexer::T_AS);
1229
        }
1230
1231 28
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1232
1233 28
        $class = $this->em->getClassMetadata($abstractSchemaName);
1234
1235
        // Building queryComponent
1236
        $queryComponent = [
1237 28
            'metadata'     => $class,
1238
            'parent'       => null,
1239
            'relation'     => null,
1240
            'map'          => null,
1241 28
            'nestingLevel' => $this->nestingLevel,
1242 28
            'token'        => $token,
1243
        ];
1244
1245 28
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1246
1247 28
        $this->match(Lexer::T_SET);
1248
1249 28
        $updateItems = [];
1250 28
        $updateItems[] = $this->UpdateItem();
1251
1252 28
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1253 3
            $this->match(Lexer::T_COMMA);
1254
1255 3
            $updateItems[] = $this->UpdateItem();
1256
        }
1257
1258 28
        $updateClause = new AST\UpdateClause($abstractSchemaName, $updateItems);
1259 28
        $updateClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1260
1261 28
        return $updateClause;
1262
    }
1263
1264
    /**
1265
     * DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable
1266
     *
1267
     * @return \Doctrine\ORM\Query\AST\DeleteClause
1268
     */
1269 35
    public function DeleteClause()
1270
    {
1271 35
        $this->match(Lexer::T_DELETE);
1272
1273 35
        if ($this->lexer->isNextToken(Lexer::T_FROM)) {
1274 6
            $this->match(Lexer::T_FROM);
1275
        }
1276
1277 35
        $token = $this->lexer->lookahead;
1278 35
        $abstractSchemaName = $this->AbstractSchemaName();
1279
1280 35
        $this->validateAbstractSchemaName($abstractSchemaName);
1281
1282 35
        $deleteClause = new AST\DeleteClause($abstractSchemaName);
1283
1284 35
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1285
            $this->match(Lexer::T_AS);
1286
        }
1287
1288 35
        $aliasIdentificationVariable = $this->lexer->isNextToken(Lexer::T_IDENTIFIER)
1289 33
            ? $this->AliasIdentificationVariable()
1290 35
            : 'alias_should_have_been_set';
1291
1292 35
        $deleteClause->aliasIdentificationVariable = $aliasIdentificationVariable;
1293 35
        $class = $this->em->getClassMetadata($deleteClause->abstractSchemaName);
1294
1295
        // Building queryComponent
1296
        $queryComponent = [
1297 35
            'metadata'     => $class,
1298
            'parent'       => null,
1299
            'relation'     => null,
1300
            'map'          => null,
1301 35
            'nestingLevel' => $this->nestingLevel,
1302 35
            'token'        => $token,
1303
        ];
1304
1305 35
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1306
1307 35
        return $deleteClause;
1308
    }
1309
1310
    /**
1311
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
1312
     *
1313
     * @return \Doctrine\ORM\Query\AST\FromClause
1314
     */
1315 456
    public function FromClause()
1316
    {
1317 456
        $this->match(Lexer::T_FROM);
1318
1319 451
        $identificationVariableDeclarations = [];
1320 451
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1321
1322 431
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1323 6
            $this->match(Lexer::T_COMMA);
1324
1325 6
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
1326
        }
1327
1328 431
        return new AST\FromClause($identificationVariableDeclarations);
1329
    }
1330
1331
    /**
1332
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
1333
     *
1334
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
1335
     */
1336 42
    public function SubselectFromClause()
1337
    {
1338 42
        $this->match(Lexer::T_FROM);
1339
1340 42
        $identificationVariables = [];
1341 42
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1342
1343 41
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1344
            $this->match(Lexer::T_COMMA);
1345
1346
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
1347
        }
1348
1349 41
        return new AST\SubselectFromClause($identificationVariables);
1350
    }
1351
1352
    /**
1353
     * WhereClause ::= "WHERE" ConditionalExpression
1354
     *
1355
     * @return \Doctrine\ORM\Query\AST\WhereClause
1356
     */
1357 245
    public function WhereClause()
1358
    {
1359 245
        $this->match(Lexer::T_WHERE);
1360
1361 245
        return new AST\WhereClause($this->ConditionalExpression());
1362
    }
1363
1364
    /**
1365
     * HavingClause ::= "HAVING" ConditionalExpression
1366
     *
1367
     * @return \Doctrine\ORM\Query\AST\HavingClause
1368
     */
1369 15
    public function HavingClause()
1370
    {
1371 15
        $this->match(Lexer::T_HAVING);
1372
1373 15
        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 23
    public function GroupByClause()
1382
    {
1383 23
        $this->match(Lexer::T_GROUP);
1384 23
        $this->match(Lexer::T_BY);
1385
1386 23
        $groupByItems = [$this->GroupByItem()];
1387
1388 22
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1389 1
            $this->match(Lexer::T_COMMA);
1390
1391 1
            $groupByItems[] = $this->GroupByItem();
1392
        }
1393
1394 22
        return new AST\GroupByClause($groupByItems);
1395
    }
1396
1397
    /**
1398
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
1399
     *
1400
     * @return \Doctrine\ORM\Query\AST\OrderByClause
1401
     */
1402 54
    public function OrderByClause()
1403
    {
1404 54
        $this->match(Lexer::T_ORDER);
1405 54
        $this->match(Lexer::T_BY);
1406
1407 54
        $orderByItems = [];
1408 54
        $orderByItems[] = $this->OrderByItem();
1409
1410 54
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1411 5
            $this->match(Lexer::T_COMMA);
1412
1413 5
            $orderByItems[] = $this->OrderByItem();
1414
        }
1415
1416 54
        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 42
    public function Subselect()
1425
    {
1426
        // Increase query nesting level
1427 42
        $this->nestingLevel++;
1428
1429 42
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
1430
1431 41
        $subselect->whereClause   = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
1432 41
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
1433 41
        $subselect->havingClause  = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
1434 41
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
1435
1436
        // Decrease query nesting level
1437 41
        $this->nestingLevel--;
1438
1439 41
        return $subselect;
1440
    }
1441
1442
    /**
1443
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
1444
     *
1445
     * @return \Doctrine\ORM\Query\AST\UpdateItem
1446
     */
1447 28
    public function UpdateItem()
1448
    {
1449 28
        $pathExpr = $this->SingleValuedPathExpression();
1450
1451 28
        $this->match(Lexer::T_EQUALS);
1452
1453 28
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
1454
1455 28
        return $updateItem;
1456
    }
1457
1458
    /**
1459
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
1460
     *
1461
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
1462
     */
1463 23
    public function GroupByItem()
1464
    {
1465
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
1466 23
        $glimpse = $this->lexer->glimpse();
1467
1468 23
        if ($glimpse !== null && $glimpse['type'] === Lexer::T_DOT) {
1469 12
            return $this->SingleValuedPathExpression();
1470
        }
1471
1472
        // Still need to decide between IdentificationVariable or ResultVariable
1473 11
        $lookaheadValue = $this->lexer->lookahead['value'];
1474
1475 11
        if ( ! isset($this->queryComponents[$lookaheadValue])) {
1476 1
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1477
        }
1478
1479 10
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1480 8
            ? $this->IdentificationVariable()
1481 10
            : $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 54
    public function OrderByItem()
1493
    {
1494 54
        $this->lexer->peek(); // lookahead => '.'
1495 54
        $this->lexer->peek(); // lookahead => token after '.'
1496
1497 54
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1498
1499 54
        $this->lexer->resetPeek();
1500
1501 54
        $glimpse = $this->lexer->glimpse();
1502
1503
        switch (true) {
1504 54
            case ($this->isFunction()):
1505 2
                $expr = $this->FunctionDeclaration();
1506 2
                break;
1507
1508 52
            case ($this->isMathOperator($peek)):
1509 5
                $expr = $this->SimpleArithmeticExpression();
1510 5
                break;
1511
1512 48
            case $glimpse !== null && $glimpse['type'] === Lexer::T_DOT:
1513 37
                $expr = $this->SingleValuedPathExpression();
1514 37
                break;
1515
1516 14
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
1517 2
                $expr = $this->ScalarExpression();
1518 2
                break;
1519
1520
            default:
1521 12
                $expr = $this->ResultVariable();
1522 12
                break;
1523
        }
1524
1525 54
        $type = 'ASC';
1526 54
        $item = new AST\OrderByItem($expr);
1527
1528
        switch (true) {
1529 54
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1530 22
                $this->match(Lexer::T_DESC);
1531 22
                $type = 'DESC';
1532 22
                break;
1533
1534 36
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1535 14
                $this->match(Lexer::T_ASC);
1536 14
                break;
1537
1538
            default:
1539
                // Do nothing
1540
        }
1541
1542 54
        $item->type = $type;
1543
1544 54
        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 28
    public function NewValue()
1561
    {
1562 28
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1563 1
            $this->match(Lexer::T_NULL);
1564
1565 1
            return null;
1566
        }
1567
1568 27
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
1569 17
            $this->match(Lexer::T_INPUT_PARAMETER);
1570
1571 17
            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 10
        return $this->ArithmeticExpression();
1575
    }
1576
1577
    /**
1578
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1579
     *
1580
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1581
     */
1582 453
    public function IdentificationVariableDeclaration()
1583
    {
1584 453
        $joins                    = [];
1585 453
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1586 436
        $indexBy                  = $this->lexer->isNextToken(Lexer::T_INDEX)
1587 7
            ? $this->IndexBy()
1588 436
            : null;
1589
1590 436
        $rangeVariableDeclaration->isRoot = true;
1591
1592
        while (
1593 436
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1594 436
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1595 436
            $this->lexer->isNextToken(Lexer::T_JOIN)
1596
        ) {
1597 113
            $joins[] = $this->Join();
1598
        }
1599
1600 433
        return new AST\IdentificationVariableDeclaration(
1601 433
            $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 42
    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 42
        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 113
    public function Join()
1672
    {
1673
        // Check Join type
1674 113
        $joinType = AST\Join::JOIN_TYPE_INNER;
1675
1676
        switch (true) {
1677 113
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1678 32
                $this->match(Lexer::T_LEFT);
1679
1680 32
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1681
1682
                // Possible LEFT OUTER join
1683 32
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1684
                    $this->match(Lexer::T_OUTER);
1685
1686
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1687
                }
1688 32
                break;
1689
1690 85
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1691 12
                $this->match(Lexer::T_INNER);
1692 12
                break;
1693
1694
            default:
1695
                // Do nothing
1696
        }
1697
1698 113
        $this->match(Lexer::T_JOIN);
1699
1700 113
        $next            = $this->lexer->glimpse();
1701 113
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
1702 110
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1703 110
        $join            = new AST\Join($joinType, $joinDeclaration);
1704
1705
        // Describe non-root join declaration
1706 110
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1707 20
            $joinDeclaration->isRoot = false;
1708
        }
1709
1710
        // Check for ad-hoc Join conditions
1711 110
        if ($adhocConditions) {
1712 21
            $this->match(Lexer::T_WITH);
1713
1714 21
            $join->conditionalExpression = $this->ConditionalExpression();
1715
        }
1716
1717 110
        return $join;
1718
    }
1719
1720
    /**
1721
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1722
     *
1723
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1724
     *
1725
     * @throws QueryException
1726
     */
1727 453
    public function RangeVariableDeclaration()
1728
    {
1729 453
        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 452
        $abstractSchemaName = $this->AbstractSchemaName();
1734
1735 451
        $this->validateAbstractSchemaName($abstractSchemaName);
1736
1737 436
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1738
            $this->match(Lexer::T_AS);
1739
        }
1740
1741 436
        $token = $this->lexer->lookahead;
1742 436
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1743 436
        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
1744
1745
        // Building queryComponent
1746
        $queryComponent = [
1747 436
            'metadata'     => $classMetadata,
1748
            'parent'       => null,
1749
            'relation'     => null,
1750
            'map'          => null,
1751 436
            'nestingLevel' => $this->nestingLevel,
1752 436
            'token'        => $token
1753
        ];
1754
1755 436
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1756
1757 436
        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 93
    public function JoinAssociationDeclaration()
1766
    {
1767 93
        $joinAssociationPathExpression = $this->JoinAssociationPathExpression();
1768
1769 93
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1770
            $this->match(Lexer::T_AS);
1771
        }
1772
1773 93
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1774 91
        $indexBy                     = $this->lexer->isNextToken(Lexer::T_INDEX) ? $this->IndexBy() : null;
1775
1776 91
        $identificationVariable = $joinAssociationPathExpression->identificationVariable;
1777 91
        $field                  = $joinAssociationPathExpression->associationField;
1778
1779 91
        $class       = $this->queryComponents[$identificationVariable]['metadata'];
1780 91
        $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1781
1782
        // Building queryComponent
1783
        $joinQueryComponent = [
1784 91
            'metadata'     => $targetClass,
1785 91
            'parent'       => $joinAssociationPathExpression->identificationVariable,
1786 91
            'relation'     => $class->getAssociationMapping($field),
1787
            'map'          => null,
1788 91
            'nestingLevel' => $this->nestingLevel,
1789 91
            'token'        => $this->lexer->lookahead
1790
        ];
1791
1792 91
        $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1793
1794 91
        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 8
    public function PartialObjectExpression()
1804
    {
1805 8
        $this->match(Lexer::T_PARTIAL);
1806
1807 8
        $partialFieldSet = [];
1808
1809 8
        $identificationVariable = $this->IdentificationVariable();
1810
1811 8
        $this->match(Lexer::T_DOT);
1812 8
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1813 8
        $this->match(Lexer::T_IDENTIFIER);
1814
1815 8
        $field = $this->lexer->token['value'];
1816
1817
        // First field in partial expression might be embeddable property
1818 8
        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 8
        $partialFieldSet[] = $field;
1825
1826 8
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1827 6
            $this->match(Lexer::T_COMMA);
1828 6
            $this->match(Lexer::T_IDENTIFIER);
1829
1830 6
            $field = $this->lexer->token['value'];
1831
1832 6
            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 6
            $partialFieldSet[] = $field;
1839
        }
1840
1841 8
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1842
1843 8
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1844
1845
        // Defer PartialObjectExpression validation
1846 8
        $this->deferredPartialObjectExpressions[] = [
1847 8
            'expression'   => $partialObjectExpression,
1848 8
            'nestingLevel' => $this->nestingLevel,
1849 8
            'token'        => $this->lexer->token,
1850
        ];
1851
1852 8
        return $partialObjectExpression;
1853
    }
1854
1855
    /**
1856
     * NewObjectExpression ::= "NEW" AbstractSchemaName "(" NewObjectArg {"," NewObjectArg}* ")"
1857
     *
1858
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1859
     */
1860 3
    public function NewObjectExpression()
1861
    {
1862 3
        $this->match(Lexer::T_NEW);
1863
1864 3
        $className = $this->AbstractSchemaName(); // note that this is not yet validated
1865 3
        $token = $this->lexer->token;
1866
1867 3
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1868
1869 3
        $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 3
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1872 3
            $this->match(Lexer::T_COMMA);
1873
1874 3
            $args[] = $this->NewObjectArg();
1875
        }
1876
1877 3
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1878
1879 3
        $expression = new AST\NewObjectExpression($className, $args);
1880
1881
        // Defer NewObjectExpression validation
1882 3
        $this->deferredNewObjectExpressions[] = [
1883 3
            'token'        => $token,
1884 3
            'expression'   => $expression,
1885 3
            'nestingLevel' => $this->nestingLevel,
1886
        ];
1887
1888 3
        return $expression;
1889
    }
1890
1891
    /**
1892
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1893
     *
1894
     * @return mixed
1895
     */
1896 3
    public function NewObjectArg()
1897
    {
1898 3
        $token = $this->lexer->lookahead;
1899 3
        $peek  = $this->lexer->glimpse();
1900
1901 3
        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 3
        return $this->ScalarExpression();
1910
    }
1911
1912
    /**
1913
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1914
     *
1915
     * @return \Doctrine\ORM\Query\AST\IndexBy
1916
     */
1917 8
    public function IndexBy()
1918
    {
1919 8
        $this->match(Lexer::T_INDEX);
1920 8
        $this->match(Lexer::T_BY);
1921 8
        $pathExpr = $this->StateFieldPathExpression();
1922
1923
        // Add the INDEX BY info to the query component
1924 8
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1925
1926 8
        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 118
    public function ScalarExpression()
1937
    {
1938 118
        $lookahead = $this->lexer->lookahead['type'];
1939 118
        $peek      = $this->lexer->glimpse();
1940
1941
        switch (true) {
1942 118
            case ($lookahead === Lexer::T_INTEGER):
1943 116
            case ($lookahead === Lexer::T_FLOAT):
1944
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1945 116
            case ($lookahead === Lexer::T_MINUS):
1946 116
            case ($lookahead === Lexer::T_PLUS):
1947 15
                return $this->SimpleArithmeticExpression();
1948
1949 116
            case ($lookahead === Lexer::T_STRING):
1950 11
                return $this->StringPrimary();
1951
1952 114
            case ($lookahead === Lexer::T_TRUE):
1953 114
            case ($lookahead === Lexer::T_FALSE):
1954 2
                $this->match($lookahead);
1955
1956 2
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1957
1958 114
            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 114
            case ($lookahead === Lexer::T_CASE):
1968 110
            case ($lookahead === Lexer::T_COALESCE):
1969 110
            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 7
                return $this->CaseExpression();
1973
1974 110
            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 107
            case ($this->isFunction()):
1979 20
                $this->lexer->peek(); // "("
1980
1981
                switch (true) {
1982 20
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
1983
                        // SUM(u.id) + COUNT(u.id)
1984 6
                        return $this->SimpleArithmeticExpression();
1985
1986
                    default:
1987
                        // IDENTITY(u)
1988 16
                        return $this->FunctionDeclaration();
1989
                }
1990
1991
                break;
1992
            // it is no function, so it must be a field path
1993 93
            case ($lookahead === Lexer::T_IDENTIFIER):
1994 93
                $this->lexer->peek(); // lookahead => '.'
1995 93
                $this->lexer->peek(); // lookahead => token after '.'
1996 93
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1997 93
                $this->lexer->resetPeek();
1998
1999 93
                if ($this->isMathOperator($peek)) {
2000 3
                    return $this->SimpleArithmeticExpression();
2001
                }
2002
2003 91
                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 17
    public function CaseExpression()
2023
    {
2024 17
        $lookahead = $this->lexer->lookahead['type'];
2025
2026
        switch ($lookahead) {
2027 17
            case Lexer::T_NULLIF:
2028 5
                return $this->NullIfExpression();
2029
2030 14
            case Lexer::T_COALESCE:
2031 2
                return $this->CoalesceExpression();
2032
2033 12
            case Lexer::T_CASE:
2034 12
                $this->lexer->resetPeek();
2035 12
                $peek = $this->lexer->peek();
2036
2037 12
                if ($peek['type'] === Lexer::T_WHEN) {
2038 7
                    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 7
    public function GeneralCaseExpression()
2101
    {
2102 7
        $this->match(Lexer::T_CASE);
2103
2104
        // Process WhenClause (1..N)
2105 7
        $whenClauses = [];
2106
2107
        do {
2108 7
            $whenClauses[] = $this->WhenClause();
2109 7
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
2110
2111 7
        $this->match(Lexer::T_ELSE);
2112 7
        $scalarExpression = $this->ScalarExpression();
2113 7
        $this->match(Lexer::T_END);
2114
2115 7
        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 7
    public function WhenClause()
2149
    {
2150 7
        $this->match(Lexer::T_WHEN);
2151 7
        $conditionalExpression = $this->ConditionalExpression();
2152 7
        $this->match(Lexer::T_THEN);
2153
2154 7
        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 465
    public function SelectExpression()
2180
    {
2181 465
        $expression    = null;
2182 465
        $identVariable = null;
2183 465
        $peek          = $this->lexer->glimpse();
2184 465
        $lookaheadType = $this->lexer->lookahead['type'];
2185
2186
        switch (true) {
2187
            // ScalarExpression (u.name)
2188 465
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
2189 84
                $expression = $this->ScalarExpression();
2190 84
                break;
2191
2192
            // IdentificationVariable (u)
2193 407
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
2194 337
                $expression = $identVariable = $this->IdentificationVariable();
2195 337
                break;
2196
2197
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
2198 109
            case ($lookaheadType === Lexer::T_CASE):
2199 104
            case ($lookaheadType === Lexer::T_COALESCE):
2200 102
            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 100
            case ($this->isFunction()):
2206 54
                $this->lexer->peek(); // "("
2207
2208
                switch (true) {
2209 54
                    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 52
                        $expression = $this->FunctionDeclaration();
2217 52
                        break;
2218
                }
2219
2220 54
                break;
2221
2222
            // PartialObjectExpression (PARTIAL u.{id, name})
2223 47
            case ($lookaheadType === Lexer::T_PARTIAL):
2224 8
                $expression    = $this->PartialObjectExpression();
2225 8
                $identVariable = $expression->identificationVariable;
2226 8
                break;
2227
2228
            // Subselect
2229 39
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
2230 19
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2231 19
                $expression = $this->Subselect();
2232 19
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2233 19
                break;
2234
2235
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
2236 20
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
2237 18
            case ($lookaheadType === Lexer::T_INTEGER):
2238 16
            case ($lookaheadType === Lexer::T_STRING):
2239 7
            case ($lookaheadType === Lexer::T_FLOAT):
2240
            // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
2241 7
            case ($lookaheadType === Lexer::T_MINUS):
2242 7
            case ($lookaheadType === Lexer::T_PLUS):
2243 14
                $expression = $this->SimpleArithmeticExpression();
2244 14
                break;
2245
2246
            // NewObjectExpression (New ClassName(id, name))
2247 6
            case ($lookaheadType === Lexer::T_NEW):
2248 3
                $expression = $this->NewObjectExpression();
2249 3
                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 462
        $mustHaveAliasResultVariable = false;
2260
2261 462
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2262 66
            $this->match(Lexer::T_AS);
2263
2264 66
            $mustHaveAliasResultVariable = true;
2265
        }
2266
2267 462
        $hiddenAliasResultVariable = false;
2268
2269 462
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
2270 6
            $this->match(Lexer::T_HIDDEN);
2271
2272 6
            $hiddenAliasResultVariable = true;
2273
        }
2274
2275 462
        $aliasResultVariable = null;
2276
2277 462
        if ($mustHaveAliasResultVariable || $this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
2278 77
            $token = $this->lexer->lookahead;
2279 77
            $aliasResultVariable = $this->AliasResultVariable();
2280
2281
            // Include AliasResultVariable in query components.
2282 72
            $this->queryComponents[$aliasResultVariable] = [
2283 72
                'resultVariable' => $expression,
2284 72
                'nestingLevel'   => $this->nestingLevel,
2285 72
                'token'          => $token,
2286
            ];
2287
        }
2288
2289
        // AST
2290
2291 457
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
2292
2293 457
        if ($identVariable) {
2294 343
            $this->identVariableExpressions[$identVariable] = $expr;
2295
        }
2296
2297 457
        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 42
    public function SimpleSelectExpression()
2309
    {
2310 42
        $peek = $this->lexer->glimpse();
2311
2312 42
        switch ($this->lexer->lookahead['type']) {
2313 42
            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 26
            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 23
        $this->lexer->peek();
2362
2363 23
        $expression = $this->ScalarExpression();
2364 23
        $expr       = new AST\SimpleSelectExpression($expression);
2365
2366 23
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
2367 1
            $this->match(Lexer::T_AS);
2368
        }
2369
2370 23
        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 23
        return $expr;
2384
    }
2385
2386
    /**
2387
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
2388
     *
2389
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
2390
     */
2391 278
    public function ConditionalExpression()
2392
    {
2393 278
        $conditionalTerms = [];
2394 278
        $conditionalTerms[] = $this->ConditionalTerm();
2395
2396 275
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
2397 12
            $this->match(Lexer::T_OR);
2398
2399 12
            $conditionalTerms[] = $this->ConditionalTerm();
2400
        }
2401
2402
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
2403
        // if only one AST\ConditionalTerm is defined
2404 275
        if (count($conditionalTerms) == 1) {
2405 269
            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 12
        return new AST\ConditionalExpression($conditionalTerms);
2409
    }
2410
2411
    /**
2412
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
2413
     *
2414
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
2415
     */
2416 278
    public function ConditionalTerm()
2417
    {
2418 278
        $conditionalFactors = [];
2419 278
        $conditionalFactors[] = $this->ConditionalFactor();
2420
2421 275
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
2422 24
            $this->match(Lexer::T_AND);
2423
2424 24
            $conditionalFactors[] = $this->ConditionalFactor();
2425
        }
2426
2427
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
2428
        // if only one AST\ConditionalFactor is defined
2429 275
        if (count($conditionalFactors) == 1) {
2430 264
            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 24
        return new AST\ConditionalTerm($conditionalFactors);
2434
    }
2435
2436
    /**
2437
     * ConditionalFactor ::= ["NOT"] ConditionalPrimary
2438
     *
2439
     * @return \Doctrine\ORM\Query\AST\ConditionalFactor
2440
     */
2441 278
    public function ConditionalFactor()
2442
    {
2443 278
        $not = false;
2444
2445 278
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2446 6
            $this->match(Lexer::T_NOT);
2447
2448 6
            $not = true;
2449
        }
2450
2451 278
        $conditionalPrimary = $this->ConditionalPrimary();
2452
2453
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
2454
        // if only one AST\ConditionalPrimary is defined
2455 275
        if ( ! $not) {
2456 273
            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 278
    public function ConditionalPrimary()
2471
    {
2472 278
        $condPrimary = new AST\ConditionalPrimary;
2473
2474 278
        if ( ! $this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2475 271
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2476
2477 268
            return $condPrimary;
2478
        }
2479
2480
        // Peek beyond the matching closing parenthesis ')'
2481 21
        $peek = $this->peekBeyondClosingParenthesis();
2482
2483 21
        if ($peek !== null && (
2484 18
            in_array($peek['value'], ['=', '<', '<=', '<>', '>', '>=', '!=']) ||
2485 14
            in_array($peek['type'], [Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS]) ||
2486 21
            $this->isMathOperator($peek)
2487
        )) {
2488 13
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
2489
2490 13
            return $condPrimary;
2491
        }
2492
2493 17
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2494 17
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
2495 17
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2496
2497 17
        return $condPrimary;
2498
    }
2499
2500
    /**
2501
     * SimpleConditionalExpression ::=
2502
     *      ComparisonExpression | BetweenExpression | LikeExpression |
2503
     *      InExpression | NullComparisonExpression | ExistsExpression |
2504
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
2505
     *      InstanceOfExpression
2506
     */
2507 278
    public function SimpleConditionalExpression()
2508
    {
2509 278
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
2510 7
            return $this->ExistsExpression();
2511
        }
2512
2513 278
        $token      = $this->lexer->lookahead;
2514 278
        $peek       = $this->lexer->glimpse();
2515 278
        $lookahead  = $token;
2516
2517 278
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2518
            $token = $this->lexer->glimpse();
2519
        }
2520
2521 278
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
2522
            // Peek beyond the matching closing parenthesis.
2523 258
            $beyond = $this->lexer->peek();
2524
2525 258
            switch ($peek['value']) {
2526 258
                case '(':
2527
                    // Peeks beyond the matched closing parenthesis.
2528 26
                    $token = $this->peekBeyondClosingParenthesis(false);
2529
2530 26
                    if ($token['type'] === Lexer::T_NOT) {
2531 3
                        $token = $this->lexer->peek();
2532
                    }
2533
2534 26
                    if ($token['type'] === Lexer::T_IS) {
2535 2
                        $lookahead = $this->lexer->peek();
2536
                    }
2537 26
                    break;
2538
2539
                default:
2540
                    // Peek beyond the PathExpression or InputParameter.
2541 238
                    $token = $beyond;
2542
2543 238
                    while ($token['value'] === '.') {
2544 202
                        $this->lexer->peek();
2545
2546 202
                        $token = $this->lexer->peek();
2547
                    }
2548
2549
                    // Also peek beyond a NOT if there is one.
2550 238
                    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 238
                    $lookahead = $this->lexer->peek();
2556
            }
2557
2558
            // Also peek beyond a NOT if there is one.
2559 258
            if ($lookahead['type'] === Lexer::T_NOT) {
2560 7
                $lookahead = $this->lexer->peek();
2561
            }
2562
2563 258
            $this->lexer->resetPeek();
2564
        }
2565
2566 278
        if ($token['type'] === Lexer::T_BETWEEN) {
2567 8
            return $this->BetweenExpression();
2568
        }
2569
2570 272
        if ($token['type'] === Lexer::T_LIKE) {
2571 13
            return $this->LikeExpression();
2572
        }
2573
2574 260
        if ($token['type'] === Lexer::T_IN) {
2575 27
            return $this->InExpression();
2576
        }
2577
2578 239
        if ($token['type'] === Lexer::T_INSTANCE) {
2579 15
            return $this->InstanceOfExpression();
2580
        }
2581
2582 224
        if ($token['type'] === Lexer::T_MEMBER) {
2583 8
            return $this->CollectionMemberExpression();
2584
        }
2585
2586 216
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2587 14
            return $this->NullComparisonExpression();
2588
        }
2589
2590 206
        if ($token['type'] === Lexer::T_IS  && $lookahead['type'] === Lexer::T_EMPTY) {
2591 3
            return $this->EmptyCollectionComparisonExpression();
2592
        }
2593
2594 203
        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 131
    public function Literal()
2658
    {
2659 131
        switch ($this->lexer->lookahead['type']) {
2660 131
            case Lexer::T_STRING:
2661 36
                $this->match(Lexer::T_STRING);
2662
2663 36
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2664 102
            case Lexer::T_INTEGER:
2665 6
            case Lexer::T_FLOAT:
2666 96
                $this->match(
2667 96
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2668
                );
2669
2670 96
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2671 6
            case Lexer::T_TRUE:
2672 3
            case Lexer::T_FALSE:
2673 6
                $this->match(
2674 6
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2675
                );
2676
2677 6
                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 19
    public function InParameter()
2689
    {
2690 19
        if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2691 9
            return $this->InputParameter();
2692
        }
2693
2694 10
        return $this->Literal();
2695
    }
2696
2697
    /**
2698
     * InputParameter ::= PositionalParameter | NamedParameter
2699
     *
2700
     * @return \Doctrine\ORM\Query\AST\InputParameter
2701
     */
2702 102
    public function InputParameter()
2703
    {
2704 102
        $this->match(Lexer::T_INPUT_PARAMETER);
2705
2706 102
        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 232
    public function ArithmeticExpression()
2715
    {
2716 232
        $expr = new AST\ArithmeticExpression;
2717
2718 232
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2719 17
            $peek = $this->lexer->glimpse();
2720
2721 17
            if ($peek['type'] === Lexer::T_SELECT) {
2722 5
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2723 5
                $expr->subselect = $this->Subselect();
2724 5
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2725
2726 5
                return $expr;
2727
            }
2728
        }
2729
2730 232
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2731
2732 232
        return $expr;
2733
    }
2734
2735
    /**
2736
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2737
     *
2738
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2739
     */
2740 292
    public function SimpleArithmeticExpression()
2741
    {
2742 292
        $terms = [];
2743 292
        $terms[] = $this->ArithmeticTerm();
2744
2745 292
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2746 17
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2747
2748 17
            $terms[] = $this->lexer->token['value'];
2749 17
            $terms[] = $this->ArithmeticTerm();
2750
        }
2751
2752
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2753
        // if only one AST\ArithmeticTerm is defined
2754 292
        if (count($terms) == 1) {
2755 290
            return $terms[0];
2756
        }
2757
2758 17
        return new AST\SimpleArithmeticExpression($terms);
2759
    }
2760
2761
    /**
2762
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2763
     *
2764
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2765
     */
2766 292
    public function ArithmeticTerm()
2767
    {
2768 292
        $factors = [];
2769 292
        $factors[] = $this->ArithmeticFactor();
2770
2771 292
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2772 27
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2773
2774 27
            $factors[] = $this->lexer->token['value'];
2775 27
            $factors[] = $this->ArithmeticFactor();
2776
        }
2777
2778
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2779
        // if only one AST\ArithmeticFactor is defined
2780 292
        if (count($factors) == 1) {
2781 288
            return $factors[0];
2782
        }
2783
2784 27
        return new AST\ArithmeticTerm($factors);
2785
    }
2786
2787
    /**
2788
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2789
     *
2790
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2791
     */
2792 292
    public function ArithmeticFactor()
2793
    {
2794 292
        $sign = null;
2795
2796 292
        if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2797 2
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2798 2
            $sign = $isPlus;
2799
        }
2800
2801 292
        $primary = $this->ArithmeticPrimary();
2802
2803
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2804
        // if only one AST\ArithmeticPrimary is defined
2805 292
        if ($sign === null) {
2806 291
            return $primary;
2807
        }
2808
2809 2
        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 292
    public function ArithmeticPrimary()
2819
    {
2820 292
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2821 21
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2822
2823 21
            $expr = $this->SimpleArithmeticExpression();
2824
2825 21
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2826
2827 21
            return new AST\ParenthesisExpression($expr);
2828
        }
2829
2830 292
        switch ($this->lexer->lookahead['type']) {
2831 292
            case Lexer::T_COALESCE:
2832 292
            case Lexer::T_NULLIF:
2833 292
            case Lexer::T_CASE:
2834 3
                return $this->CaseExpression();
2835
2836 292
            case Lexer::T_IDENTIFIER:
2837 267
                $peek = $this->lexer->glimpse();
2838
2839 267
                if ($peek !== null && $peek['value'] === '(') {
2840 23
                    return $this->FunctionDeclaration();
2841
                }
2842
2843 251
                if ($peek !== null && $peek['value'] === '.') {
0 ignored issues
show
introduced by
The condition $peek !== null is always false.
Loading history...
2844 248
                    return $this->SingleValuedPathExpression();
2845
                }
2846
2847 31
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2848 10
                    return $this->ResultVariable();
2849
                }
2850
2851 23
                return $this->StateFieldPathExpression();
2852
2853 204
            case Lexer::T_INPUT_PARAMETER:
2854 85
                return $this->InputParameter();
2855
2856
            default:
2857 126
                $peek = $this->lexer->glimpse();
2858
2859 126
                if ($peek !== null && $peek['value'] === '(') {
2860 11
                    return $this->FunctionDeclaration();
2861
                }
2862
2863 123
                return $this->Literal();
2864
        }
2865
    }
2866
2867
    /**
2868
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2869
     *
2870
     * @return \Doctrine\ORM\Query\AST\Subselect |
2871
     *         string
2872
     */
2873 13
    public function StringExpression()
2874
    {
2875 13
        $peek = $this->lexer->glimpse();
2876
2877
        // Subselect
2878 13
        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 13
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2888 13
            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 11
        return $this->StringPrimary();
2893
    }
2894
2895
    /**
2896
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2897
     */
2898 38
    public function StringPrimary()
2899
    {
2900 38
        $lookaheadType = $this->lexer->lookahead['type'];
2901
2902
        switch ($lookaheadType) {
2903 38
            case Lexer::T_IDENTIFIER:
2904 25
                $peek = $this->lexer->glimpse();
2905
2906 25
                if ($peek['value'] == '.') {
2907 25
                    return $this->StateFieldPathExpression();
2908
                }
2909
2910 5
                if ($peek['value'] == '(') {
2911
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2912 5
                    return $this->FunctionDeclaration();
2913
                }
2914
2915
                $this->syntaxError("'.' or '('");
2916
                break;
2917
2918 27
            case Lexer::T_STRING:
2919 24
                $this->match(Lexer::T_STRING);
2920
2921 24
                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 65
    public function AggregateExpression()
2979
    {
2980 65
        $lookaheadType = $this->lexer->lookahead['type'];
2981 65
        $isDistinct = false;
2982
2983 65
        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 65
        $this->match($lookaheadType);
2988 65
        $functionName = $this->lexer->token['value'];
2989 65
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2990
2991 65
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2992 2
            $this->match(Lexer::T_DISTINCT);
2993 2
            $isDistinct = true;
2994
        }
2995
2996 65
        $pathExp = $this->SimpleArithmeticExpression();
2997
2998 65
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2999
3000 65
        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 203
    public function ComparisonExpression()
3060
    {
3061 203
        $this->lexer->glimpse();
3062
3063 203
        $leftExpr  = $this->ArithmeticExpression();
3064 203
        $operator  = $this->ComparisonOperator();
3065 203
        $rightExpr = ($this->isNextAllAnySome())
3066 3
            ? $this->QuantifiedExpression()
3067 203
            : $this->ArithmeticExpression();
3068
3069 201
        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 27
    public function InExpression()
3078
    {
3079 27
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
3080
3081 27
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3082 6
            $this->match(Lexer::T_NOT);
3083 6
            $inExpression->not = true;
3084
        }
3085
3086 27
        $this->match(Lexer::T_IN);
3087 27
        $this->match(Lexer::T_OPEN_PARENTHESIS);
3088
3089 27
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
3090 8
            $inExpression->subselect = $this->Subselect();
3091
        } else {
3092 19
            $literals = [];
3093 19
            $literals[] = $this->InParameter();
3094
3095 19
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
3096 14
                $this->match(Lexer::T_COMMA);
3097 14
                $literals[] = $this->InParameter();
3098
            }
3099
3100 19
            $inExpression->literals = $literals;
3101
        }
3102
3103 26
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
3104
3105 26
        return $inExpression;
3106
    }
3107
3108
    /**
3109
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
3110
     *
3111
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
3112
     */
3113 15
    public function InstanceOfExpression()
3114
    {
3115 15
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
3116
3117 15
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3118 1
            $this->match(Lexer::T_NOT);
3119 1
            $instanceOfExpression->not = true;
3120
        }
3121
3122 15
        $this->match(Lexer::T_INSTANCE);
3123 15
        $this->match(Lexer::T_OF);
3124
3125 15
        $exprValues = [];
3126
3127 15
        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 13
        $exprValues[] = $this->InstanceOfParameter();
3146
3147 13
        $instanceOfExpression->value = $exprValues;
3148
3149 13
        return $instanceOfExpression;
3150
    }
3151
3152
    /**
3153
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
3154
     *
3155
     * @return mixed
3156
     */
3157 15
    public function InstanceOfParameter()
3158
    {
3159 15
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3160 4
            $this->match(Lexer::T_INPUT_PARAMETER);
3161
3162 4
            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 13
    public function LikeExpression()
3178
    {
3179 13
        $stringExpr = $this->StringExpression();
3180 13
        $not = false;
3181
3182 13
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3183 3
            $this->match(Lexer::T_NOT);
3184 3
            $not = true;
3185
        }
3186
3187 13
        $this->match(Lexer::T_LIKE);
3188
3189 13
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
3190 3
            $this->match(Lexer::T_INPUT_PARAMETER);
3191 3
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
3192
        } else {
3193 11
            $stringPattern = $this->StringPrimary();
3194
        }
3195
3196 13
        $escapeChar = null;
3197
3198 13
        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 13
        $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 13
        $likeExpr->not = $not;
3207
3208 13
        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 14
    public function NullComparisonExpression()
3217
    {
3218
        switch (true) {
3219 14
            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 14
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
3226 1
                $expr = $this->NullIfExpression();
3227 1
                break;
3228
3229 14
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
3230 1
                $expr = $this->CoalesceExpression();
3231 1
                break;
3232
3233 14
            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 13
                $glimpse = $this->lexer->glimpse();
3240
3241 13
                if ($glimpse['type'] === Lexer::T_DOT) {
3242 10
                    $expr = $this->SingleValuedPathExpression();
3243
3244
                    // Leave switch statement
3245 10
                    break;
3246
                }
3247
3248 3
                $lookaheadValue = $this->lexer->lookahead['value'];
3249
3250
                // Validate existing component
3251 3
                if ( ! isset($this->queryComponents[$lookaheadValue])) {
3252
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
3253
                }
3254
3255
                // Validate SingleValuedPathExpression (ie.: "product")
3256 3
                if (isset($this->queryComponents[$lookaheadValue]['metadata'])) {
3257
                    $expr = $this->SingleValuedPathExpression();
3258
                    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 14
        $nullCompExpr = new AST\NullComparisonExpression($expr);
0 ignored issues
show
Bug introduced by
It seems like $expr can also be of type string; however, parameter $expression of Doctrine\ORM\Query\AST\N...pression::__construct() does only seem to accept Doctrine\ORM\Query\AST\Node, maybe add an additional type check? ( Ignorable by Annotation )

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

3270
        $nullCompExpr = new AST\NullComparisonExpression(/** @scrutinizer ignore-type */ $expr);
Loading history...
3271
3272 14
        $this->match(Lexer::T_IS);
3273
3274 14
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
3275 6
            $this->match(Lexer::T_NOT);
3276
3277 6
            $nullCompExpr->not = true;
3278
        }
3279
3280 14
        $this->match(Lexer::T_NULL);
3281
3282 14
        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 203
    public function ComparisonOperator()
3316
    {
3317 203
        switch ($this->lexer->lookahead['value']) {
3318 203
            case '=':
3319 157
                $this->match(Lexer::T_EQUALS);
3320
3321 157
                return '=';
3322
3323 56
            case '<':
3324 16
                $this->match(Lexer::T_LOWER_THAN);
3325 16
                $operator = '<';
3326
3327 16
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3328 5
                    $this->match(Lexer::T_EQUALS);
3329 5
                    $operator .= '=';
3330 11
                } else if ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
3331 3
                    $this->match(Lexer::T_GREATER_THAN);
3332 3
                    $operator .= '>';
3333
                }
3334
3335 16
                return $operator;
3336
3337 48
            case '>':
3338 41
                $this->match(Lexer::T_GREATER_THAN);
3339 41
                $operator = '>';
3340
3341 41
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
3342 6
                    $this->match(Lexer::T_EQUALS);
3343 6
                    $operator .= '=';
3344
                }
3345
3346 41
                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 100
    public function FunctionDeclaration()
3365
    {
3366 100
        $token = $this->lexer->lookahead;
3367 100
        $funcName = strtolower($token['value']);
3368
3369 100
        $customFunctionDeclaration = $this->CustomFunctionDeclaration();
3370
3371
        // Check for custom functions functions first!
3372
        switch (true) {
3373 100
            case $customFunctionDeclaration !== null:
3374 3
                return $customFunctionDeclaration;
3375
3376 97
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
3377 22
                return $this->FunctionsReturningStrings();
3378
3379 78
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
3380 75
                return $this->FunctionsReturningNumerics();
3381
3382 3
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
3383 3
                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 100
    private function CustomFunctionDeclaration()
3396
    {
3397 100
        $token = $this->lexer->lookahead;
3398 100
        $funcName = strtolower($token['value']);
3399
3400
        // Check for custom functions afterwards
3401 100
        $config = $this->em->getConfiguration();
3402
3403
        switch (true) {
3404 100
            case ($config->getCustomStringFunction($funcName) !== null):
3405 2
                return $this->CustomFunctionsReturningStrings();
3406
3407 98
            case ($config->getCustomNumericFunction($funcName) !== null):
3408 1
                return $this->CustomFunctionsReturningNumerics();
3409
3410 97
            case ($config->getCustomDatetimeFunction($funcName) !== null):
3411
                return $this->CustomFunctionsReturningDatetime();
3412
3413
            default:
3414 97
                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 75
    public function FunctionsReturningNumerics()
3433
    {
3434 75
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3435 75
        $funcClass     = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
3436
3437 75
        $function = new $funcClass($funcNameLower);
3438 75
        $function->parse($this);
3439
3440 75
        return $function;
3441
    }
3442
3443
    /**
3444
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3445
     */
3446 1
    public function CustomFunctionsReturningNumerics()
3447
    {
3448
        // getCustomNumericFunction is case-insensitive
3449 1
        $functionName  = strtolower($this->lexer->lookahead['value']);
3450 1
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
3451
3452 1
        $function = is_string($functionClass)
3453 1
            ? new $functionClass($functionName)
3454 1
            : call_user_func($functionClass, $functionName);
3455
3456 1
        $function->parse($this);
3457
3458 1
        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 3
    public function FunctionsReturningDatetime()
3472
    {
3473 3
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3474 3
        $funcClass     = self::$_DATETIME_FUNCTIONS[$funcNameLower];
3475
3476 3
        $function = new $funcClass($funcNameLower);
3477 3
        $function->parse($this);
3478
3479 3
        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 22
    public function FunctionsReturningStrings()
3512
    {
3513 22
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
3514 22
        $funcClass     = self::$_STRING_FUNCTIONS[$funcNameLower];
3515
3516 22
        $function = new $funcClass($funcNameLower);
3517 22
        $function->parse($this);
3518
3519 22
        return $function;
3520
    }
3521
3522
    /**
3523
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
3524
     */
3525 2
    public function CustomFunctionsReturningStrings()
3526
    {
3527
        // getCustomStringFunction is case-insensitive
3528 2
        $functionName  = $this->lexer->lookahead['value'];
3529 2
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
3530
3531 2
        $function = is_string($functionClass)
3532 2
            ? new $functionClass($functionName)
3533 2
            : call_user_func($functionClass, $functionName);
3534
3535 2
        $function->parse($this);
3536
3537 2
        return $function;
3538
    }
3539
}
3540