GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 619fa8...db31ee )
by Anderson
02:20
created

QueryParser::UpdateStatement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 7
Ratio 100 %

Importance

Changes 0
Metric Value
dl 7
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
namespace DoctrineElastic\Query;
4
5
use Doctrine\ORM\Query;
6
use Doctrine\ORM\Query\Lexer;
7
use Doctrine\ORM\Query\ParserResult;
8
use Doctrine\ORM\Query\QueryException;
9
use Doctrine\ORM\Query\TreeWalker;
10
use DoctrineElastic\Elastic\ElasticQuery;
11
use Doctrine\ORM\Query\AST;
12
13
/**
14
 * Elastic Query parser
15
 * Prepares query for execution
16
 *
17
 * @author Ands
18
 */
19
class QueryParser {
20
21
    /** @var ElasticQuery */
22
    protected $query;
23
24
    /** @var Query\AST\SelectStatement */
25
    private $_ast;
26
27
    public function __construct(ElasticQuery $query) {
28
        $this->query = $query;
29
        $this->em = $query->getEntityManager();
0 ignored issues
show
Documentation Bug introduced by
It seems like $query->getEntityManager() of type object<DoctrineElastic\ElasticEntityManager> is incompatible with the declared type object<Doctrine\ORM\EntityManager> of property $em.

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...
30
        $this->lexer = new Lexer($query->getDQL());
31
        $this->parserResult = new ParserResult();
32
    }
33
34
    public function getAST() {
35
        if (is_null($this->_ast)) {
36
            $this->_ast = $this->QueryLanguage();
37
        }
38
39
        return $this->_ast;
40
    }
41
42
    /**
43
     * Converts ElasticQuery to SearchParams
44
     *
45
     * @return \DoctrineElastic\Elastic\SearchParams
46
     */
47
    public function parseElasticQuery() {
48
        $outputWalker = new ElasticWalker($this->query, $this->getAST(), $this->getRootClass());
49
50
        return $outputWalker->walkSelectStatement();
51
    }
52
53
    /**
54
     * @return string
55
     */
56
    public function getRootClass() {
57
        /** @var \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration[] $identificationVariableDeclarations */
58
        $identificationVariableDeclarations = $this->getAST()->fromClause->identificationVariableDeclarations;
59
60
        foreach ($identificationVariableDeclarations as $idVarDeclaration) {
61
            if ($idVarDeclaration->rangeVariableDeclaration->isRoot) {
62
                return $idVarDeclaration->rangeVariableDeclaration->abstractSchemaName;
63
            }
64
        }
65
66
        return reset($identificationVariableDeclarations)->rangeVariableDeclaration->abstractSchemaName;
67
    }
68
69
70
71
72
73
74
    /** Doctrine Parser methods copy */
75
76
    /**
77
     * READ-ONLY: Maps BUILT-IN string function names to AST class names.
78
     *
79
     * @var array
80
     */
81
    private static $_STRING_FUNCTIONS = array(
82
        'concat' => 'Doctrine\ORM\Query\AST\Functions\ConcatFunction',
83
        'substring' => 'Doctrine\ORM\Query\AST\Functions\SubstringFunction',
84
        'trim' => 'Doctrine\ORM\Query\AST\Functions\TrimFunction',
85
        'lower' => 'Doctrine\ORM\Query\AST\Functions\LowerFunction',
86
        'upper' => 'Doctrine\ORM\Query\AST\Functions\UpperFunction',
87
        'identity' => 'Doctrine\ORM\Query\AST\Functions\IdentityFunction',
88
    );
89
90
    /**
91
     * READ-ONLY: Maps BUILT-IN numeric function names to AST class names.
92
     *
93
     * @var array
94
     */
95
    private static $_NUMERIC_FUNCTIONS = array(
96
        'length' => 'Doctrine\ORM\Query\AST\Functions\LengthFunction',
97
        'locate' => 'Doctrine\ORM\Query\AST\Functions\LocateFunction',
98
        'abs' => 'Doctrine\ORM\Query\AST\Functions\AbsFunction',
99
        'sqrt' => 'Doctrine\ORM\Query\AST\Functions\SqrtFunction',
100
        'mod' => 'Doctrine\ORM\Query\AST\Functions\ModFunction',
101
        'size' => 'Doctrine\ORM\Query\AST\Functions\SizeFunction',
102
        'date_diff' => 'Doctrine\ORM\Query\AST\Functions\DateDiffFunction',
103
        'bit_and' => 'Doctrine\ORM\Query\AST\Functions\BitAndFunction',
104
        'bit_or' => 'Doctrine\ORM\Query\AST\Functions\BitOrFunction',
105
    );
106
107
    /**
108
     * READ-ONLY: Maps BUILT-IN datetime function names to AST class names.
109
     *
110
     * @var array
111
     */
112
    private static $_DATETIME_FUNCTIONS = array(
113
        'current_date' => 'Doctrine\ORM\Query\AST\Functions\CurrentDateFunction',
114
        'current_time' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimeFunction',
115
        'current_timestamp' => 'Doctrine\ORM\Query\AST\Functions\CurrentTimestampFunction',
116
        'date_add' => 'Doctrine\ORM\Query\AST\Functions\DateAddFunction',
117
        'date_sub' => 'Doctrine\ORM\Query\AST\Functions\DateSubFunction',
118
    );
119
120
    /*
121
     * Expressions that were encountered during parsing of identifiers and expressions
122
     * and still need to be validated.
123
     */
124
125
    /**
126
     * @var array
127
     */
128
    private $deferredIdentificationVariables = array();
129
130
    /**
131
     * @var array
132
     */
133
    private $deferredPartialObjectExpressions = array();
134
135
    /**
136
     * @var array
137
     */
138
    private $deferredPathExpressions = array();
139
140
    /**
141
     * @var array
142
     */
143
    private $deferredResultVariables = array();
144
145
    /**
146
     * @var array
147
     */
148
    private $deferredNewObjectExpressions = array();
149
150
    /**
151
     * The lexer.
152
     *
153
     * @var \Doctrine\ORM\Query\Lexer
154
     */
155
    private $lexer;
156
157
    /**
158
     * The parser result.
159
     *
160
     * @var \Doctrine\ORM\Query\ParserResult
161
     */
162
    private $parserResult;
163
164
    /**
165
     * The EntityManager.
166
     *
167
     * @var \Doctrine\ORM\EntityManager
168
     */
169
    private $em;
170
171
    /**
172
     * Map of declared query components in the parsed query.
173
     *
174
     * @var array
175
     */
176
    private $queryComponents = array();
177
178
    /**
179
     * Keeps the nesting level of defined ResultVariables.
180
     *
181
     * @var integer
182
     */
183
    private $nestingLevel = 0;
184
185
    /**
186
     * Any additional custom tree walkers that modify the AST.
187
     *
188
     * @var array
189
     */
190
    private $customTreeWalkers = array();
191
192
    /**
193
     * The custom last tree walker, if any, that is responsible for producing the output.
194
     *
195
     * @var TreeWalker
196
     */
197
    private $customOutputWalker;
198
199
    /**
200
     * @var array
201
     */
202
    private $identVariableExpressions = array();
203
204
    /**
205
     * Checks if a function is internally defined. Used to prevent overwriting
206
     * of built-in functions through user-defined functions.
207
     *
208
     * @param string $functionName
209
     *
210
     * @return bool
211
     */
212
    static public function isInternalFunction($functionName) {
0 ignored issues
show
Coding Style introduced by
As per PSR2, the static declaration should come after the visibility declaration.
Loading history...
213
        $functionName = strtolower($functionName);
214
215
        return isset(self::$_STRING_FUNCTIONS[$functionName])
216
        || isset(self::$_DATETIME_FUNCTIONS[$functionName])
217
        || isset(self::$_NUMERIC_FUNCTIONS[$functionName]);
218
    }
219
220
    /**
221
     * Sets a custom tree walker that produces output.
222
     * This tree walker will be run last over the AST, after any other walkers.
223
     *
224
     * @param string $className
225
     *
226
     * @return void
227
     */
228
    public function setCustomOutputTreeWalker($className) {
229
        $this->customOutputWalker = $className;
0 ignored issues
show
Documentation Bug introduced by
It seems like $className of type string is incompatible with the declared type object<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...
230
    }
231
232
    /**
233
     * Adds a custom tree walker for modifying the AST.
234
     *
235
     * @param string $className
236
     *
237
     * @return void
238
     */
239
    public function addCustomTreeWalker($className) {
240
        $this->customTreeWalkers[] = $className;
241
    }
242
243
    /**
244
     * Gets the lexer used by the parser.
245
     *
246
     * @return \Doctrine\ORM\Query\Lexer
247
     */
248
    public function getLexer() {
249
        return $this->lexer;
250
    }
251
252
    /**
253
     * Gets the ParserResult that is being filled with information during parsing.
254
     *
255
     * @return \Doctrine\ORM\Query\ParserResult
256
     */
257
    public function getParserResult() {
258
        return $this->parserResult;
259
    }
260
261
    /**
262
     * Gets the EntityManager used by the parser.
263
     *
264
     * @return \Doctrine\ORM\EntityManager
265
     */
266
    public function getEntityManager() {
267
        return $this->em;
268
    }
269
270
    /**
271
     * Attempts to match the given token with the current lookahead token.
272
     *
273
     * If they match, updates the lookahead token; otherwise raises a syntax
274
     * error.
275
     *
276
     * @param int $token The token type.
277
     *
278
     * @return void
279
     *
280
     * @throws QueryException If the tokens don't match.
281
     */
282
    public function match($token) {
283
        $lookaheadType = $this->lexer->lookahead['type'];
284
285
        // short-circuit on first condition, usually types match
286
        if ($lookaheadType !== $token && $token !== Lexer::T_IDENTIFIER && $lookaheadType <= Lexer::T_IDENTIFIER) {
287
            $this->syntaxError($this->lexer->getLiteral($token));
288
        }
289
290
        $this->lexer->moveNext();
291
    }
292
293
    /**
294
     * Frees this parser, enabling it to be reused.
295
     *
296
     * @param boolean $deep Whether to clean peek and reset errors.
297
     * @param integer $position Position to reset.
298
     *
299
     * @return void
300
     */
301
    public function free($deep = false, $position = 0) {
302
        // WARNING! Use this method with care. It resets the scanner!
303
        $this->lexer->resetPosition($position);
304
305
        // Deep = true cleans peek and also any previously defined errors
306
        if ($deep) {
307
            $this->lexer->resetPeek();
308
        }
309
310
        $this->lexer->token = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $token.

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...
311
        $this->lexer->lookahead = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $lookahead.

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...
312
    }
313
314
    /**
315
     * Parses a query string.
316
     *
317
     * @return ParserResult
318
     */
319
    public function parse() {
320
        $AST = $this->getAST();
321
322
        if (($customWalkers = $this->query->getHint(Query::HINT_CUSTOM_TREE_WALKERS)) !== false) {
0 ignored issues
show
Bug introduced by
The method getHint() does not seem to exist on object<DoctrineElastic\Elastic\ElasticQuery>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
323
            $this->customTreeWalkers = $customWalkers;
324
        }
325
326
        if (($customOutputWalker = $this->query->getHint(Query::HINT_CUSTOM_OUTPUT_WALKER)) !== false) {
0 ignored issues
show
Bug introduced by
The method getHint() does not seem to exist on object<DoctrineElastic\Elastic\ElasticQuery>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
327
            $this->customOutputWalker = $customOutputWalker;
328
        }
329
330
        // Run any custom tree walkers over the AST
331
        if ($this->customTreeWalkers) {
332
            $treeWalkerChain = new TreeWalkerChain($this->query, $this->parserResult, $this->queryComponents);
333
334
            foreach ($this->customTreeWalkers as $walker) {
335
                $treeWalkerChain->addTreeWalker($walker);
336
            }
337
338
            switch (true) {
339
                case ($AST instanceof AST\UpdateStatement):
340
                    $treeWalkerChain->walkUpdateStatement($AST);
341
                    break;
342
343
                case ($AST instanceof AST\DeleteStatement):
344
                    $treeWalkerChain->walkDeleteStatement($AST);
345
                    break;
346
347
                case ($AST instanceof AST\SelectStatement):
348
                default:
349
                    $treeWalkerChain->walkSelectStatement($AST);
350
            }
351
352
            $this->queryComponents = $treeWalkerChain->getQueryComponents();
353
        }
354
355
        $outputWalkerClass = $this->customOutputWalker ?: __NAMESPACE__ . '\SqlWalker';
356
        $outputWalker = new $outputWalkerClass($this->query, $this->parserResult, $this->queryComponents);
357
358
        // Assign an SQL executor to the parser result
359
        $this->parserResult->setSqlExecutor($outputWalker->getExecutor($AST));
360
361
        return $this->parserResult;
362
    }
363
364
    /**
365
     * Generates a new syntax error.
366
     *
367
     * @param string $expected Expected string.
368
     * @param array|null $token Got token.
369
     *
370
     * @return void
371
     *
372
     * @throws \Doctrine\ORM\Query\QueryException
373
     */
374
    public function syntaxError($expected = '', $token = null) {
375
        if ($token === null) {
376
            $token = $this->lexer->lookahead;
377
        }
378
379
        $tokenPos = (isset($token['position'])) ? $token['position'] : '-1';
380
381
        $message = "line 0, col {$tokenPos}: Error: ";
382
        $message .= ($expected !== '') ? "Expected {$expected}, got " : 'Unexpected ';
383
        $message .= ($this->lexer->lookahead === null) ? 'end of string.' : "'{$token['value']}'";
384
385
        throw QueryException::syntaxError($message, QueryException::dqlError($this->query->getDQL()));
386
    }
387
388
    /**
389
     * Generates a new semantical error.
390
     *
391
     * @param string $message Optional message.
392
     * @param array|null $token Optional token.
393
     *
394
     * @return void
395
     *
396
     * @throws \Doctrine\ORM\Query\QueryException
397
     */
398
    public function semanticalError($message = '', $token = null) {
399
        if ($token === null) {
400
            $token = $this->lexer->lookahead;
401
        }
402
403
        // Minimum exposed chars ahead of token
404
        $distance = 12;
405
406
        // Find a position of a final word to display in error string
407
        $dql = $this->query->getDql();
408
        $length = strlen($dql);
409
        $pos = $token['position'] + $distance;
410
        $pos = strpos($dql, ' ', ($length > $pos) ? $pos : $length);
411
        $length = ($pos !== false) ? $pos - $token['position'] : $distance;
412
413
        $tokenPos = (isset($token['position']) && $token['position'] > 0) ? $token['position'] : '-1';
414
        $tokenStr = substr($dql, $token['position'], $length);
415
416
        // Building informative message
417
        $message = 'line 0, col ' . $tokenPos . " near '" . $tokenStr . "': Error: " . $message;
418
419
        throw QueryException::semanticalError($message, QueryException::dqlError($this->query->getDQL()));
420
    }
421
422
    /**
423
     * Peeks beyond the matched closing parenthesis and returns the first token after that one.
424
     *
425
     * @param boolean $resetPeek Reset peek after finding the closing parenthesis.
426
     *
427
     * @return array
428
     */
429
    private function peekBeyondClosingParenthesis($resetPeek = true) {
430
        $token = $this->lexer->peek();
431
        $numUnmatched = 1;
432
433
        while ($numUnmatched > 0 && $token !== null) {
434
            switch ($token['type']) {
435
                case Lexer::T_OPEN_PARENTHESIS:
436
                    ++$numUnmatched;
437
                    break;
438
439
                case Lexer::T_CLOSE_PARENTHESIS:
440
                    --$numUnmatched;
441
                    break;
442
443
                default:
444
                    // Do nothing
445
            }
446
447
            $token = $this->lexer->peek();
448
        }
449
450
        if ($resetPeek) {
451
            $this->lexer->resetPeek();
452
        }
453
454
        return $token;
455
    }
456
457
    /**
458
     * Checks if the given token indicates a mathematical operator.
459
     *
460
     * @param array $token
461
     *
462
     * @return boolean TRUE if the token is a mathematical operator, FALSE otherwise.
463
     */
464
    private function isMathOperator($token) {
465
        return in_array($token['type'], array(Lexer::T_PLUS, Lexer::T_MINUS, Lexer::T_DIVIDE, Lexer::T_MULTIPLY));
466
    }
467
468
    /**
469
     * Checks if the next-next (after lookahead) token starts a function.
470
     *
471
     * @return boolean TRUE if the next-next tokens start a function, FALSE otherwise.
472
     */
473
    private function isFunction() {
474
        $lookaheadType = $this->lexer->lookahead['type'];
475
        $peek = $this->lexer->peek();
476
477
        $this->lexer->resetPeek();
478
479
        return ($lookaheadType >= Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_OPEN_PARENTHESIS);
480
    }
481
482
    /**
483
     * Checks whether the given token type indicates an aggregate function.
484
     *
485
     * @param int $tokenType
486
     *
487
     * @return boolean TRUE if the token type is an aggregate function, FALSE otherwise.
488
     */
489
    private function isAggregateFunction($tokenType) {
490
        return in_array($tokenType, array(Lexer::T_AVG, Lexer::T_MIN, Lexer::T_MAX, Lexer::T_SUM, Lexer::T_COUNT));
491
    }
492
493
    /**
494
     * Checks whether the current lookahead token of the lexer has the type T_ALL, T_ANY or T_SOME.
495
     *
496
     * @return boolean
497
     */
498
    private function isNextAllAnySome() {
499
        return in_array($this->lexer->lookahead['type'], array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME));
500
    }
501
502
    /**
503
     * QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement
504
     *
505
     * @return \Doctrine\ORM\Query\AST\SelectStatement |
506
     *         \Doctrine\ORM\Query\AST\UpdateStatement |
507
     *         \Doctrine\ORM\Query\AST\DeleteStatement
508
     */
509
    public function QueryLanguage() {
510
        $this->lexer->moveNext();
511
512
        switch ($this->lexer->lookahead['type']) {
513
            case Lexer::T_SELECT:
514
                $statement = $this->SelectStatement();
515
                break;
516
517
            case Lexer::T_UPDATE:
518
                $statement = $this->UpdateStatement();
519
                break;
520
521
            case Lexer::T_DELETE:
522
                $statement = $this->DeleteStatement();
523
                break;
524
525
            default:
526
                $this->syntaxError('SELECT, UPDATE or DELETE');
527
                break;
528
        }
529
530
        // Check for end of string
531
        if ($this->lexer->lookahead !== null) {
532
            $this->syntaxError('end of string');
533
        }
534
535
        return $statement;
0 ignored issues
show
Bug introduced by
The variable $statement does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug Best Practice introduced by
The return type of return $statement; (Doctrine\ORM\Query\AST\S...ery\AST\DeleteStatement) is incompatible with the return type documented by DoctrineElastic\Query\QueryParser::QueryLanguage of type Doctrine\ORM\Query\AST\SelectStatement.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
536
    }
537
538
    /**
539
     * SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
540
     *
541
     * @return \Doctrine\ORM\Query\AST\SelectStatement
542
     */
543
    public function SelectStatement() {
544
        $selectStatement = new AST\SelectStatement($this->SelectClause(), $this->FromClause());
545
546
        $selectStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
547
        $selectStatement->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
548
        $selectStatement->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
549
        $selectStatement->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
550
551
        return $selectStatement;
552
    }
553
554
    /**
555
     * UpdateStatement ::= UpdateClause [WhereClause]
556
     *
557
     * @return \Doctrine\ORM\Query\AST\UpdateStatement
558
     */
559 View Code Duplication
    public function UpdateStatement() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
560
        $updateStatement = new AST\UpdateStatement($this->UpdateClause());
0 ignored issues
show
Bug introduced by
The method UpdateClause() does not seem to exist on object<DoctrineElastic\Query\QueryParser>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
561
562
        $updateStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
563
564
        return $updateStatement;
565
    }
566
567
    /**
568
     * DeleteStatement ::= DeleteClause [WhereClause]
569
     *
570
     * @return \Doctrine\ORM\Query\AST\DeleteStatement
571
     */
572 View Code Duplication
    public function DeleteStatement() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
573
        $deleteStatement = new AST\DeleteStatement($this->DeleteClause());
0 ignored issues
show
Bug introduced by
The method DeleteClause() does not seem to exist on object<DoctrineElastic\Query\QueryParser>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
574
575
        $deleteStatement->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
576
577
        return $deleteStatement;
578
    }
579
580
    /**
581
     * IdentificationVariable ::= identifier
582
     *
583
     * @return string
584
     */
585 View Code Duplication
    public function IdentificationVariable() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
586
        $this->match(Lexer::T_IDENTIFIER);
587
588
        $identVariable = $this->lexer->token['value'];
589
590
        $this->deferredIdentificationVariables[] = array(
591
            'expression' => $identVariable,
592
            'nestingLevel' => $this->nestingLevel,
593
            'token' => $this->lexer->token,
594
        );
595
596
        return $identVariable;
597
    }
598
599
    /**
600
     * AliasIdentificationVariable = identifier
601
     *
602
     * @return string
603
     */
604 View Code Duplication
    public function AliasIdentificationVariable() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
605
        $this->match(Lexer::T_IDENTIFIER);
606
607
        $aliasIdentVariable = $this->lexer->token['value'];
608
        $exists = isset($this->queryComponents[$aliasIdentVariable]);
609
610
        if ($exists) {
611
            $this->semanticalError("'$aliasIdentVariable' is already defined.", $this->lexer->token);
612
        }
613
614
        return $aliasIdentVariable;
615
    }
616
617
    /**
618
     * AbstractSchemaName ::= identifier
619
     *
620
     * @return string
621
     */
622
    public function AbstractSchemaName() {
623
        $this->match(Lexer::T_IDENTIFIER);
624
625
        $schemaName = ltrim($this->lexer->token['value'], '\\');
626
627 View Code Duplication
        if (strrpos($schemaName, ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
628
            list($namespaceAlias, $simpleClassName) = explode(':', $schemaName);
629
630
            $schemaName = $this->em->getConfiguration()->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
631
        }
632
633
        $exists = class_exists($schemaName, true) || interface_exists($schemaName, true);
634
635
        if (!$exists) {
636
            $this->semanticalError("Class '$schemaName' is not defined.", $this->lexer->token);
637
        }
638
639
        return $schemaName;
640
    }
641
642
    /**
643
     * AliasResultVariable ::= identifier
644
     *
645
     * @return string
646
     */
647 View Code Duplication
    public function AliasResultVariable() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
648
        $this->match(Lexer::T_IDENTIFIER);
649
650
        $resultVariable = $this->lexer->token['value'];
651
        $exists = isset($this->queryComponents[$resultVariable]);
652
653
        if ($exists) {
654
            $this->semanticalError("'$resultVariable' is already defined.", $this->lexer->token);
655
        }
656
657
        return $resultVariable;
658
    }
659
660
    /**
661
     * ResultVariable ::= identifier
662
     *
663
     * @return string
664
     */
665 View Code Duplication
    public function ResultVariable() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
666
        $this->match(Lexer::T_IDENTIFIER);
667
668
        $resultVariable = $this->lexer->token['value'];
669
670
        // Defer ResultVariable validation
671
        $this->deferredResultVariables[] = array(
672
            'expression' => $resultVariable,
673
            'nestingLevel' => $this->nestingLevel,
674
            'token' => $this->lexer->token,
675
        );
676
677
        return $resultVariable;
678
    }
679
680
    /**
681
     * JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField)
682
     *
683
     * @return \Doctrine\ORM\Query\AST\JoinAssociationPathExpression
684
     */
685
    public function JoinAssociationPathExpression() {
686
        $identVariable = $this->IdentificationVariable();
687
688
        if (!isset($this->queryComponents[$identVariable])) {
689
            $this->semanticalError(
690
                'Identification Variable ' . $identVariable . ' used in join path expression but was not defined before.'
691
            );
692
        }
693
694
        $this->match(Lexer::T_DOT);
695
        $this->match(Lexer::T_IDENTIFIER);
696
697
        $field = $this->lexer->token['value'];
698
699
        // Validate association field
700
        $qComp = $this->queryComponents[$identVariable];
701
        $class = $qComp['metadata'];
702
703
        if (!$class->hasAssociation($field)) {
704
            $this->semanticalError('Class ' . $class->name . ' has no association named ' . $field);
705
        }
706
707
        return new AST\JoinAssociationPathExpression($identVariable, $field);
708
    }
709
710
    /**
711
     * Parses an arbitrary path expression and defers semantical validation
712
     * based on expected types.
713
     *
714
     * PathExpression ::= IdentificationVariable {"." identifier}*
715
     *
716
     * @param integer $expectedTypes
717
     *
718
     * @return \Doctrine\ORM\Query\AST\PathExpression
719
     */
720
    public function PathExpression($expectedTypes) {
721
        $identVariable = $this->IdentificationVariable();
722
        $field = null;
723
724
        if ($this->lexer->isNextToken(Lexer::T_DOT)) {
725
            $this->match(Lexer::T_DOT);
726
            $this->match(Lexer::T_IDENTIFIER);
727
728
            $field = $this->lexer->token['value'];
729
730 View Code Duplication
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
731
                $this->match(Lexer::T_DOT);
732
                $this->match(Lexer::T_IDENTIFIER);
733
                $field .= '.' . $this->lexer->token['value'];
734
            }
735
        }
736
737
        // Creating AST node
738
        $pathExpr = new AST\PathExpression($expectedTypes, $identVariable, $field);
739
740
        // Defer PathExpression validation if requested to be deferred
741
        $this->deferredPathExpressions[] = array(
742
            'expression' => $pathExpr,
743
            'nestingLevel' => $this->nestingLevel,
744
            'token' => $this->lexer->token,
745
        );
746
747
        return $pathExpr;
748
    }
749
750
    /**
751
     * AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression
752
     *
753
     * @return \Doctrine\ORM\Query\AST\PathExpression
754
     */
755
    public function AssociationPathExpression() {
756
        return $this->PathExpression(
757
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION |
758
            AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION
759
        );
760
    }
761
762
    /**
763
     * SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression
764
     *
765
     * @return \Doctrine\ORM\Query\AST\PathExpression
766
     */
767
    public function SingleValuedPathExpression() {
768
        return $this->PathExpression(
769
            AST\PathExpression::TYPE_STATE_FIELD |
770
            AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION
771
        );
772
    }
773
774
    /**
775
     * StateFieldPathExpression ::= IdentificationVariable "." StateField
776
     *
777
     * @return \Doctrine\ORM\Query\AST\PathExpression
778
     */
779
    public function StateFieldPathExpression() {
780
        return $this->PathExpression(AST\PathExpression::TYPE_STATE_FIELD);
781
    }
782
783
    /**
784
     * SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField
785
     *
786
     * @return \Doctrine\ORM\Query\AST\PathExpression
787
     */
788
    public function SingleValuedAssociationPathExpression() {
789
        return $this->PathExpression(AST\PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION);
790
    }
791
792
    /**
793
     * CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField
794
     *
795
     * @return \Doctrine\ORM\Query\AST\PathExpression
796
     */
797
    public function CollectionValuedPathExpression() {
798
        return $this->PathExpression(AST\PathExpression::TYPE_COLLECTION_VALUED_ASSOCIATION);
799
    }
800
801
    /**
802
     * SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}
803
     *
804
     * @return \Doctrine\ORM\Query\AST\SelectClause
805
     */
806
    public function SelectClause() {
807
        $isDistinct = false;
808
        $this->match(Lexer::T_SELECT);
809
810
        // Check for DISTINCT
811
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
812
            $this->match(Lexer::T_DISTINCT);
813
814
            $isDistinct = true;
815
        }
816
817
        // Process SelectExpressions (1..N)
818
        $selectExpressions = array();
819
        $selectExpressions[] = $this->SelectExpression();
820
821
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
822
            $this->match(Lexer::T_COMMA);
823
824
            $selectExpressions[] = $this->SelectExpression();
825
        }
826
827
        return new AST\SelectClause($selectExpressions, $isDistinct);
828
    }
829
830
    /**
831
     * SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression
832
     *
833
     * @return \Doctrine\ORM\Query\AST\SimpleSelectClause
834
     */
835 View Code Duplication
    public function SimpleSelectClause() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
836
        $isDistinct = false;
837
        $this->match(Lexer::T_SELECT);
838
839
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
840
            $this->match(Lexer::T_DISTINCT);
841
842
            $isDistinct = true;
843
        }
844
845
        return new AST\SimpleSelectClause($this->SimpleSelectExpression(), $isDistinct);
846
    }
847
848
    /**
849
     * FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}*
850
     *
851
     * @return \Doctrine\ORM\Query\AST\FromClause
852
     */
853
    public function FromClause() {
854
        $this->match(Lexer::T_FROM);
855
856
        $identificationVariableDeclarations = array();
857
        $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
858
859
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
860
            $this->match(Lexer::T_COMMA);
861
862
            $identificationVariableDeclarations[] = $this->IdentificationVariableDeclaration();
863
        }
864
865
        return new AST\FromClause($identificationVariableDeclarations);
866
    }
867
868
    /**
869
     * SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}*
870
     *
871
     * @return \Doctrine\ORM\Query\AST\SubselectFromClause
872
     */
873
    public function SubselectFromClause() {
874
        $this->match(Lexer::T_FROM);
875
876
        $identificationVariables = array();
877
        $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
878
879
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
880
            $this->match(Lexer::T_COMMA);
881
882
            $identificationVariables[] = $this->SubselectIdentificationVariableDeclaration();
883
        }
884
885
        return new AST\SubselectFromClause($identificationVariables);
886
    }
887
888
    /**
889
     * WhereClause ::= "WHERE" ConditionalExpression
890
     *
891
     * @return \Doctrine\ORM\Query\AST\WhereClause
892
     */
893
    public function WhereClause() {
894
        $this->match(Lexer::T_WHERE);
895
896
        return new AST\WhereClause($this->ConditionalExpression());
897
    }
898
899
    /**
900
     * HavingClause ::= "HAVING" ConditionalExpression
901
     *
902
     * @return \Doctrine\ORM\Query\AST\HavingClause
903
     */
904
    public function HavingClause() {
905
        $this->match(Lexer::T_HAVING);
906
907
        return new AST\HavingClause($this->ConditionalExpression());
908
    }
909
910
    /**
911
     * GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}*
912
     *
913
     * @return \Doctrine\ORM\Query\AST\GroupByClause
914
     */
915
    public function GroupByClause() {
916
        $this->match(Lexer::T_GROUP);
917
        $this->match(Lexer::T_BY);
918
919
        $groupByItems = array($this->GroupByItem());
920
921
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
922
            $this->match(Lexer::T_COMMA);
923
924
            $groupByItems[] = $this->GroupByItem();
925
        }
926
927
        return new AST\GroupByClause($groupByItems);
928
    }
929
930
    /**
931
     * OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}*
932
     *
933
     * @return \Doctrine\ORM\Query\AST\OrderByClause
934
     */
935
    public function OrderByClause() {
936
        $this->match(Lexer::T_ORDER);
937
        $this->match(Lexer::T_BY);
938
939
        $orderByItems = array();
940
        $orderByItems[] = $this->OrderByItem();
941
942
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
943
            $this->match(Lexer::T_COMMA);
944
945
            $orderByItems[] = $this->OrderByItem();
946
        }
947
948
        return new AST\OrderByClause($orderByItems);
949
    }
950
951
    /**
952
     * Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause]
953
     *
954
     * @return \Doctrine\ORM\Query\AST\Subselect
955
     */
956
    public function Subselect() {
957
        // Increase query nesting level
958
        $this->nestingLevel++;
959
960
        $subselect = new AST\Subselect($this->SimpleSelectClause(), $this->SubselectFromClause());
961
962
        $subselect->whereClause = $this->lexer->isNextToken(Lexer::T_WHERE) ? $this->WhereClause() : null;
963
        $subselect->groupByClause = $this->lexer->isNextToken(Lexer::T_GROUP) ? $this->GroupByClause() : null;
964
        $subselect->havingClause = $this->lexer->isNextToken(Lexer::T_HAVING) ? $this->HavingClause() : null;
965
        $subselect->orderByClause = $this->lexer->isNextToken(Lexer::T_ORDER) ? $this->OrderByClause() : null;
966
967
        // Decrease query nesting level
968
        $this->nestingLevel--;
969
970
        return $subselect;
971
    }
972
973
    /**
974
     * UpdateItem ::= SingleValuedPathExpression "=" NewValue
975
     *
976
     * @return \Doctrine\ORM\Query\AST\UpdateItem
977
     */
978
    public function UpdateItem() {
979
        $pathExpr = $this->SingleValuedPathExpression();
980
981
        $this->match(Lexer::T_EQUALS);
982
983
        $updateItem = new AST\UpdateItem($pathExpr, $this->NewValue());
984
985
        return $updateItem;
986
    }
987
988
    /**
989
     * GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression
990
     *
991
     * @return string | \Doctrine\ORM\Query\AST\PathExpression
992
     */
993
    public function GroupByItem() {
994
        // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
995
        $glimpse = $this->lexer->glimpse();
996
997
        if ($glimpse['type'] === Lexer::T_DOT) {
998
            return $this->SingleValuedPathExpression();
999
        }
1000
1001
        // Still need to decide between IdentificationVariable or ResultVariable
1002
        $lookaheadValue = $this->lexer->lookahead['value'];
1003
1004
        if (!isset($this->queryComponents[$lookaheadValue])) {
1005
            $this->semanticalError('Cannot group by undefined identification or result variable.');
1006
        }
1007
1008
        return (isset($this->queryComponents[$lookaheadValue]['metadata']))
1009
            ? $this->IdentificationVariable()
1010
            : $this->ResultVariable();
1011
    }
1012
1013
    /**
1014
     * OrderByItem ::= (
1015
     *      SimpleArithmeticExpression | SingleValuedPathExpression |
1016
     *      ScalarExpression | ResultVariable | FunctionDeclaration
1017
     * ) ["ASC" | "DESC"]
1018
     *
1019
     * @return \Doctrine\ORM\Query\AST\OrderByItem
1020
     */
1021
    public function OrderByItem() {
1022
        $this->lexer->peek(); // lookahead => '.'
1023
        $this->lexer->peek(); // lookahead => token after '.'
1024
1025
        $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1026
1027
        $this->lexer->resetPeek();
1028
1029
        $glimpse = $this->lexer->glimpse();
1030
1031
        switch (true) {
1032
            case ($this->isFunction($peek)):
0 ignored issues
show
Unused Code introduced by
The call to QueryParser::isFunction() has too many arguments starting with $peek.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1033
                $expr = $this->FunctionDeclaration();
1034
                break;
1035
1036
            case ($this->isMathOperator($peek)):
0 ignored issues
show
Bug introduced by
It seems like $peek defined by $this->lexer->peek() on line 1025 can also be of type null; however, DoctrineElastic\Query\Qu...arser::isMathOperator() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1037
                $expr = $this->SimpleArithmeticExpression();
1038
                break;
1039
1040
            case ($glimpse['type'] === Lexer::T_DOT):
1041
                $expr = $this->SingleValuedPathExpression();
1042
                break;
1043
1044
            case ($this->lexer->peek() && $this->isMathOperator($this->peekBeyondClosingParenthesis())):
0 ignored issues
show
Bug introduced by
It seems like $this->peekBeyondClosingParenthesis() targeting DoctrineElastic\Query\Qu...ondClosingParenthesis() can also be of type null; however, DoctrineElastic\Query\Qu...arser::isMathOperator() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1045
                $expr = $this->ScalarExpression();
1046
                break;
1047
1048
            default:
1049
                $expr = $this->ResultVariable();
1050
                break;
1051
        }
1052
1053
        $type = 'ASC';
1054
        $item = new AST\OrderByItem($expr);
1055
1056
        switch (true) {
1057
            case ($this->lexer->isNextToken(Lexer::T_DESC)):
1058
                $this->match(Lexer::T_DESC);
1059
                $type = 'DESC';
1060
                break;
1061
1062
            case ($this->lexer->isNextToken(Lexer::T_ASC)):
1063
                $this->match(Lexer::T_ASC);
1064
                break;
1065
1066
            default:
1067
                // Do nothing
1068
        }
1069
1070
        $item->type = $type;
1071
1072
        return $item;
1073
    }
1074
1075
    /**
1076
     * NewValue ::= SimpleArithmeticExpression | StringPrimary | DatetimePrimary | BooleanPrimary |
1077
     *      EnumPrimary | SimpleEntityExpression | "NULL"
1078
     *
1079
     * NOTE: Since it is not possible to correctly recognize individual types, here is the full
1080
     * grammar that needs to be supported:
1081
     *
1082
     * NewValue ::= SimpleArithmeticExpression | "NULL"
1083
     *
1084
     * SimpleArithmeticExpression covers all *Primary grammar rules and also SimpleEntityExpression
1085
     *
1086
     * @return AST\ArithmeticExpression
1087
     */
1088
    public function NewValue() {
1089
        if ($this->lexer->isNextToken(Lexer::T_NULL)) {
1090
            $this->match(Lexer::T_NULL);
1091
1092
            return null;
1093
        }
1094
1095 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1096
            $this->match(Lexer::T_INPUT_PARAMETER);
1097
1098
            return new AST\InputParameter($this->lexer->token['value']);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return new \Doctrine\ORM...lexer->token['value']); (Doctrine\ORM\Query\AST\InputParameter) is incompatible with the return type documented by DoctrineElastic\Query\QueryParser::NewValue of type Doctrine\ORM\Query\AST\ArithmeticExpression|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1099
        }
1100
1101
        return $this->ArithmeticExpression();
1102
    }
1103
1104
    /**
1105
     * IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {Join}*
1106
     *
1107
     * @return \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1108
     */
1109
    public function IdentificationVariableDeclaration() {
1110
        $joins = array();
1111
        $rangeVariableDeclaration = $this->RangeVariableDeclaration();
1112
        $indexBy = $this->lexer->isNextToken(Lexer::T_INDEX)
1113
            ? $this->IndexBy()
1114
            : null;
1115
1116
        $rangeVariableDeclaration->isRoot = true;
1117
1118
        while (
1119
            $this->lexer->isNextToken(Lexer::T_LEFT) ||
1120
            $this->lexer->isNextToken(Lexer::T_INNER) ||
1121
            $this->lexer->isNextToken(Lexer::T_JOIN)
1122
        ) {
1123
            $joins[] = $this->Join();
1124
        }
1125
1126
        return new AST\IdentificationVariableDeclaration(
1127
            $rangeVariableDeclaration, $indexBy, $joins
1128
        );
1129
    }
1130
1131
    /**
1132
     *
1133
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration
1134
     *
1135
     * {Internal note: WARNING: Solution is harder than a bare implementation.
1136
     * Desired EBNF support:
1137
     *
1138
     * SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable)
1139
     *
1140
     * It demands that entire SQL generation to become programmatical. This is
1141
     * needed because association based subselect requires "WHERE" conditional
1142
     * expressions to be injected, but there is no scope to do that. Only scope
1143
     * accessible is "FROM", prohibiting an easy implementation without larger
1144
     * changes.}
1145
     *
1146
     * @return \Doctrine\ORM\Query\AST\SubselectIdentificationVariableDeclaration |
1147
     *         \Doctrine\ORM\Query\AST\IdentificationVariableDeclaration
1148
     */
1149
    public function SubselectIdentificationVariableDeclaration() {
1150
        /*
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1151
        NOT YET IMPLEMENTED!
1152
1153
        $glimpse = $this->lexer->glimpse();
1154
1155
        if ($glimpse['type'] == Lexer::T_DOT) {
1156
            $associationPathExpression = $this->AssociationPathExpression();
1157
1158
            if ($this->lexer->isNextToken(Lexer::T_AS)) {
1159
                $this->match(Lexer::T_AS);
1160
            }
1161
1162
            $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1163
            $identificationVariable      = $associationPathExpression->identificationVariable;
1164
            $field                       = $associationPathExpression->associationField;
1165
1166
            $class       = $this->queryComponents[$identificationVariable]['metadata'];
1167
            $targetClass = $this->em->getClassMetadata($class->associationMappings[$field]['targetEntity']);
1168
1169
            // Building queryComponent
1170
            $joinQueryComponent = array(
1171
                'metadata'     => $targetClass,
1172
                'parent'       => $identificationVariable,
1173
                'relation'     => $class->getAssociationMapping($field),
1174
                'map'          => null,
1175
                'nestingLevel' => $this->nestingLevel,
1176
                'token'        => $this->lexer->lookahead
1177
            );
1178
1179
            $this->queryComponents[$aliasIdentificationVariable] = $joinQueryComponent;
1180
1181
            return new AST\SubselectIdentificationVariableDeclaration(
1182
                $associationPathExpression, $aliasIdentificationVariable
1183
            );
1184
        }
1185
        */
1186
1187
        return $this->IdentificationVariableDeclaration();
1188
    }
1189
1190
    /**
1191
     * Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN"
1192
     *          (JoinAssociationDeclaration | RangeVariableDeclaration)
1193
     *          ["WITH" ConditionalExpression]
1194
     *
1195
     * @return \Doctrine\ORM\Query\AST\Join
1196
     */
1197
    public function Join() {
1198
        // Check Join type
1199
        $joinType = AST\Join::JOIN_TYPE_INNER;
1200
1201
        switch (true) {
1202
            case ($this->lexer->isNextToken(Lexer::T_LEFT)):
1203
                $this->match(Lexer::T_LEFT);
1204
1205
                $joinType = AST\Join::JOIN_TYPE_LEFT;
1206
1207
                // Possible LEFT OUTER join
1208
                if ($this->lexer->isNextToken(Lexer::T_OUTER)) {
1209
                    $this->match(Lexer::T_OUTER);
1210
1211
                    $joinType = AST\Join::JOIN_TYPE_LEFTOUTER;
1212
                }
1213
                break;
1214
1215
            case ($this->lexer->isNextToken(Lexer::T_INNER)):
1216
                $this->match(Lexer::T_INNER);
1217
                break;
1218
1219
            default:
1220
                // Do nothing
1221
        }
1222
1223
        $this->match(Lexer::T_JOIN);
1224
1225
        $next = $this->lexer->glimpse();
1226
        $joinDeclaration = ($next['type'] === Lexer::T_DOT) ? $this->JoinAssociationDeclaration() : $this->RangeVariableDeclaration();
0 ignored issues
show
Bug introduced by
The method JoinAssociationDeclaration() does not seem to exist on object<DoctrineElastic\Query\QueryParser>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1227
        $adhocConditions = $this->lexer->isNextToken(Lexer::T_WITH);
1228
        $join = new AST\Join($joinType, $joinDeclaration);
1229
1230
        // Describe non-root join declaration
1231
        if ($joinDeclaration instanceof AST\RangeVariableDeclaration) {
1232
            $joinDeclaration->isRoot = false;
1233
1234
            $adhocConditions = true;
1235
        }
1236
1237
        // Check for ad-hoc Join conditions
1238
        if ($adhocConditions) {
1239
            $this->match(Lexer::T_WITH);
1240
1241
            $join->conditionalExpression = $this->ConditionalExpression();
1242
        }
1243
1244
        return $join;
1245
    }
1246
1247
    /**
1248
     * RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable
1249
     *
1250
     * @return \Doctrine\ORM\Query\AST\RangeVariableDeclaration
1251
     */
1252
    public function RangeVariableDeclaration() {
1253
        $abstractSchemaName = $this->AbstractSchemaName();
1254
1255
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1256
            $this->match(Lexer::T_AS);
1257
        }
1258
1259
        $token = $this->lexer->lookahead;
1260
        $aliasIdentificationVariable = $this->AliasIdentificationVariable();
1261
//        $classMetadata = $this->em->getClassMetadata($abstractSchemaName);
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1262
1263
        // Building queryComponent
1264
        $queryComponent = array(
1265
//            'metadata' => $classMetadata,
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1266
            'parent' => null,
1267
            'relation' => null,
1268
            'map' => null,
1269
            'nestingLevel' => $this->nestingLevel,
1270
            'token' => $token
1271
        );
1272
1273
        $this->queryComponents[$aliasIdentificationVariable] = $queryComponent;
1274
1275
        return new AST\RangeVariableDeclaration($abstractSchemaName, $aliasIdentificationVariable);
1276
    }
1277
1278
    /**
1279
     * PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet
1280
     * PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}"
1281
     *
1282
     * @return array
1283
     */
1284
    public function PartialObjectExpression() {
1285
        $this->match(Lexer::T_PARTIAL);
1286
1287
        $partialFieldSet = array();
1288
1289
        $identificationVariable = $this->IdentificationVariable();
1290
1291
        $this->match(Lexer::T_DOT);
1292
        $this->match(Lexer::T_OPEN_CURLY_BRACE);
1293
        $this->match(Lexer::T_IDENTIFIER);
1294
1295
        $partialFieldSet[] = $this->lexer->token['value'];
1296
1297
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1298
            $this->match(Lexer::T_COMMA);
1299
            $this->match(Lexer::T_IDENTIFIER);
1300
1301
            $field = $this->lexer->token['value'];
1302
1303 View Code Duplication
            while ($this->lexer->isNextToken(Lexer::T_DOT)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1304
                $this->match(Lexer::T_DOT);
1305
                $this->match(Lexer::T_IDENTIFIER);
1306
                $field .= '.' . $this->lexer->token['value'];
1307
            }
1308
1309
            $partialFieldSet[] = $field;
1310
        }
1311
1312
        $this->match(Lexer::T_CLOSE_CURLY_BRACE);
1313
1314
        $partialObjectExpression = new AST\PartialObjectExpression($identificationVariable, $partialFieldSet);
1315
1316
        // Defer PartialObjectExpression validation
1317
        $this->deferredPartialObjectExpressions[] = array(
1318
            'expression' => $partialObjectExpression,
1319
            'nestingLevel' => $this->nestingLevel,
1320
            'token' => $this->lexer->token,
1321
        );
1322
1323
        return $partialObjectExpression;
1324
    }
1325
1326
    /**
1327
     * NewObjectExpression ::= "NEW" IdentificationVariable "(" NewObjectArg {"," NewObjectArg}* ")"
1328
     *
1329
     * @return \Doctrine\ORM\Query\AST\NewObjectExpression
1330
     */
1331
    public function NewObjectExpression() {
1332
        $this->match(Lexer::T_NEW);
1333
        $this->match(Lexer::T_IDENTIFIER);
1334
1335
        $token = $this->lexer->token;
1336
        $className = $token['value'];
1337
1338 View Code Duplication
        if (strrpos($className, ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1339
            list($namespaceAlias, $simpleClassName) = explode(':', $className);
1340
1341
            $className = $this->em->getConfiguration()
1342
                    ->getEntityNamespace($namespaceAlias) . '\\' . $simpleClassName;
1343
        }
1344
1345
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1346
1347
        $args[] = $this->NewObjectArg();
0 ignored issues
show
Coding Style Comprehensibility introduced by
$args was never initialized. Although not strictly required by PHP, it is generally a good practice to add $args = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1348
1349
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1350
            $this->match(Lexer::T_COMMA);
1351
1352
            $args[] = $this->NewObjectArg();
1353
        }
1354
1355
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1356
1357
        $expression = new AST\NewObjectExpression($className, $args);
1358
1359
        // Defer NewObjectExpression validation
1360
        $this->deferredNewObjectExpressions[] = array(
1361
            'token' => $token,
1362
            'expression' => $expression,
1363
            'nestingLevel' => $this->nestingLevel,
1364
        );
1365
1366
        return $expression;
1367
    }
1368
1369
    /**
1370
     * NewObjectArg ::= ScalarExpression | "(" Subselect ")"
1371
     *
1372
     * @return mixed
1373
     */
1374
    public function NewObjectArg() {
1375
        $token = $this->lexer->lookahead;
1376
        $peek = $this->lexer->glimpse();
1377
1378
        if ($token['type'] === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT) {
1379
            $this->match(Lexer::T_OPEN_PARENTHESIS);
1380
            $expression = $this->Subselect();
1381
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
1382
1383
            return $expression;
1384
        }
1385
1386
        return $this->ScalarExpression();
1387
    }
1388
1389
    /**
1390
     * IndexBy ::= "INDEX" "BY" StateFieldPathExpression
1391
     *
1392
     * @return \Doctrine\ORM\Query\AST\IndexBy
1393
     */
1394
    public function IndexBy() {
1395
        $this->match(Lexer::T_INDEX);
1396
        $this->match(Lexer::T_BY);
1397
        $pathExpr = $this->StateFieldPathExpression();
1398
1399
        // Add the INDEX BY info to the query component
1400
        $this->queryComponents[$pathExpr->identificationVariable]['map'] = $pathExpr->field;
1401
1402
        return new AST\IndexBy($pathExpr);
1403
    }
1404
1405
    /**
1406
     * ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary |
1407
     *                      StateFieldPathExpression | BooleanPrimary | CaseExpression |
1408
     *                      InstanceOfExpression
1409
     *
1410
     * @return mixed One of the possible expressions or subexpressions.
1411
     */
1412
    public function ScalarExpression() {
1413
        $lookahead = $this->lexer->lookahead['type'];
1414
        $peek = $this->lexer->glimpse();
1415
1416
        switch (true) {
1417
            case ($lookahead === Lexer::T_INTEGER):
1418
            case ($lookahead === Lexer::T_FLOAT):
1419
                // SimpleArithmeticExpression : (- u.value ) or ( + u.value )  or ( - 1 ) or ( + 1 )
1420
            case ($lookahead === Lexer::T_MINUS):
1421
            case ($lookahead === Lexer::T_PLUS):
1422
                return $this->SimpleArithmeticExpression();
1423
1424
            case ($lookahead === Lexer::T_STRING):
1425
                return $this->StringPrimary();
1426
1427
            case ($lookahead === Lexer::T_TRUE):
1428 View Code Duplication
            case ($lookahead === Lexer::T_FALSE):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1429
                $this->match($lookahead);
1430
1431
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
1432
1433
            case ($lookahead === Lexer::T_INPUT_PARAMETER):
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
1434
                switch (true) {
1435
                    case $this->isMathOperator($peek):
0 ignored issues
show
Bug introduced by
It seems like $peek defined by $this->lexer->glimpse() on line 1414 can also be of type null; however, DoctrineElastic\Query\Qu...arser::isMathOperator() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1436
                        // :param + u.value
1437
                        return $this->SimpleArithmeticExpression();
1438
1439
                    default:
1440
                        return $this->InputParameter();
1441
                }
1442
1443
            case ($lookahead === Lexer::T_CASE):
1444
            case ($lookahead === Lexer::T_COALESCE):
1445
            case ($lookahead === Lexer::T_NULLIF):
1446
                // Since NULLIF and COALESCE can be identified as a function,
1447
                // we need to check these before checking for FunctionDeclaration
1448
                return $this->CaseExpression();
1449
1450
            case ($lookahead === Lexer::T_OPEN_PARENTHESIS):
1451
                return $this->SimpleArithmeticExpression();
1452
1453
            // this check must be done before checking for a filed path expression
1454
            case ($this->isFunction()):
1455
                $this->lexer->peek(); // "("
1456
1457
                switch (true) {
1458
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
0 ignored issues
show
Bug introduced by
It seems like $this->peekBeyondClosingParenthesis() targeting DoctrineElastic\Query\Qu...ondClosingParenthesis() can also be of type null; however, DoctrineElastic\Query\Qu...arser::isMathOperator() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1459
                        // SUM(u.id) + COUNT(u.id)
1460
                        return $this->SimpleArithmeticExpression();
1461
1462
                    case ($this->isAggregateFunction($this->lexer->lookahead['type'])):
1463
                        return $this->AggregateExpression();
1464
1465
                    default:
1466
                        // IDENTITY(u)
1467
                        return $this->FunctionDeclaration();
1468
                }
1469
1470
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
1471
            // it is no function, so it must be a field path
1472
            case ($lookahead === Lexer::T_IDENTIFIER):
1473
                $this->lexer->peek(); // lookahead => '.'
1474
                $this->lexer->peek(); // lookahead => token after '.'
1475
                $peek = $this->lexer->peek(); // lookahead => token after the token after the '.'
1476
                $this->lexer->resetPeek();
1477
1478
                if ($this->isMathOperator($peek)) {
0 ignored issues
show
Bug introduced by
It seems like $peek defined by $this->lexer->peek() on line 1475 can also be of type null; however, DoctrineElastic\Query\Qu...arser::isMathOperator() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1479
                    return $this->SimpleArithmeticExpression();
1480
                }
1481
1482
                return $this->StateFieldPathExpression();
1483
1484
            default:
1485
                $this->syntaxError();
1486
        }
1487
    }
1488
1489
    /**
1490
     * CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression
1491
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1492
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1493
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1494
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1495
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1496
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1497
     * NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1498
     *
1499
     * @return mixed One of the possible expressions or subexpressions.
1500
     */
1501
    public function CaseExpression() {
1502
        $lookahead = $this->lexer->lookahead['type'];
1503
1504
        switch ($lookahead) {
1505
            case Lexer::T_NULLIF:
1506
                return $this->NullIfExpression();
1507
1508
            case Lexer::T_COALESCE:
1509
                return $this->CoalesceExpression();
1510
1511
            case Lexer::T_CASE:
1512
                $this->lexer->resetPeek();
1513
                $peek = $this->lexer->peek();
1514
1515
                if ($peek['type'] === Lexer::T_WHEN) {
1516
                    return $this->GeneralCaseExpression();
1517
                }
1518
1519
                return $this->SimpleCaseExpression();
1520
1521
            default:
1522
                // Do nothing
1523
                break;
1524
        }
1525
1526
        $this->syntaxError();
1527
    }
1528
1529
    /**
1530
     * CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")"
1531
     *
1532
     * @return \Doctrine\ORM\Query\AST\CoalesceExpression
1533
     */
1534
    public function CoalesceExpression() {
1535
        $this->match(Lexer::T_COALESCE);
1536
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1537
1538
        // Process ScalarExpressions (1..N)
1539
        $scalarExpressions = array();
1540
        $scalarExpressions[] = $this->ScalarExpression();
1541
1542
        while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
1543
            $this->match(Lexer::T_COMMA);
1544
1545
            $scalarExpressions[] = $this->ScalarExpression();
1546
        }
1547
1548
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1549
1550
        return new AST\CoalesceExpression($scalarExpressions);
1551
    }
1552
1553
    /**
1554
     * NullIfExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")"
1555
     *
1556
     * @return \Doctrine\ORM\Query\AST\NullIfExpression
1557
     */
1558
    public function NullIfExpression() {
1559
        $this->match(Lexer::T_NULLIF);
1560
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1561
1562
        $firstExpression = $this->ScalarExpression();
1563
        $this->match(Lexer::T_COMMA);
1564
        $secondExpression = $this->ScalarExpression();
1565
1566
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1567
1568
        return new AST\NullIfExpression($firstExpression, $secondExpression);
1569
    }
1570
1571
    /**
1572
     * GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END"
1573
     *
1574
     * @return \Doctrine\ORM\Query\AST\GeneralCaseExpression
1575
     */
1576 View Code Duplication
    public function GeneralCaseExpression() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1577
        $this->match(Lexer::T_CASE);
1578
1579
        // Process WhenClause (1..N)
1580
        $whenClauses = array();
1581
1582
        do {
1583
            $whenClauses[] = $this->WhenClause();
1584
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
1585
1586
        $this->match(Lexer::T_ELSE);
1587
        $scalarExpression = $this->ScalarExpression();
1588
        $this->match(Lexer::T_END);
1589
1590
        return new AST\GeneralCaseExpression($whenClauses, $scalarExpression);
1591
    }
1592
1593
    /**
1594
     * SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END"
1595
     * CaseOperand ::= StateFieldPathExpression | TypeDiscriminator
1596
     *
1597
     * @return AST\SimpleCaseExpression
1598
     */
1599 View Code Duplication
    public function SimpleCaseExpression() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1600
        $this->match(Lexer::T_CASE);
1601
        $caseOperand = $this->StateFieldPathExpression();
1602
1603
        // Process SimpleWhenClause (1..N)
1604
        $simpleWhenClauses = array();
1605
1606
        do {
1607
            $simpleWhenClauses[] = $this->SimpleWhenClause();
1608
        } while ($this->lexer->isNextToken(Lexer::T_WHEN));
1609
1610
        $this->match(Lexer::T_ELSE);
1611
        $scalarExpression = $this->ScalarExpression();
1612
        $this->match(Lexer::T_END);
1613
1614
        return new AST\SimpleCaseExpression($caseOperand, $simpleWhenClauses, $scalarExpression);
1615
    }
1616
1617
    /**
1618
     * WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression
1619
     *
1620
     * @return \Doctrine\ORM\Query\AST\WhenClause
1621
     */
1622
    public function WhenClause() {
1623
        $this->match(Lexer::T_WHEN);
1624
        $conditionalExpression = $this->ConditionalExpression();
1625
        $this->match(Lexer::T_THEN);
1626
1627
        return new AST\WhenClause($conditionalExpression, $this->ScalarExpression());
1628
    }
1629
1630
    /**
1631
     * SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression
1632
     *
1633
     * @return \Doctrine\ORM\Query\AST\SimpleWhenClause
1634
     */
1635
    public function SimpleWhenClause() {
1636
        $this->match(Lexer::T_WHEN);
1637
        $conditionalExpression = $this->ScalarExpression();
1638
        $this->match(Lexer::T_THEN);
1639
1640
        return new AST\SimpleWhenClause($conditionalExpression, $this->ScalarExpression());
1641
    }
1642
1643
    /**
1644
     * SelectExpression ::= (
1645
     *     IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration |
1646
     *     PartialObjectExpression | "(" Subselect ")" | CaseExpression | NewObjectExpression
1647
     * ) [["AS"] ["HIDDEN"] AliasResultVariable]
1648
     *
1649
     * @return \Doctrine\ORM\Query\AST\SelectExpression
1650
     */
1651
    public function SelectExpression() {
1652
        $expression = null;
1653
        $identVariable = null;
1654
        $peek = $this->lexer->glimpse();
1655
        $lookaheadType = $this->lexer->lookahead['type'];
1656
1657
        switch (true) {
1658
            // ScalarExpression (u.name)
1659
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] === Lexer::T_DOT):
1660
                $expression = $this->ScalarExpression();
1661
                break;
1662
1663
            // IdentificationVariable (u)
1664
            case ($lookaheadType === Lexer::T_IDENTIFIER && $peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
1665
                $expression = $identVariable = $this->IdentificationVariable();
1666
                break;
1667
1668
            // CaseExpression (CASE ... or NULLIF(...) or COALESCE(...))
0 ignored issues
show
Unused Code Comprehensibility introduced by
55% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1669
            case ($lookaheadType === Lexer::T_CASE):
1670
            case ($lookaheadType === Lexer::T_COALESCE):
1671
            case ($lookaheadType === Lexer::T_NULLIF):
1672
                $expression = $this->CaseExpression();
1673
                break;
1674
1675
            // DQL Function (SUM(u.value) or SUM(u.value) + 1)
1676
            case ($this->isFunction()):
1677
                $this->lexer->peek(); // "("
1678
1679
                switch (true) {
1680
                    case ($this->isMathOperator($this->peekBeyondClosingParenthesis())):
0 ignored issues
show
Bug introduced by
It seems like $this->peekBeyondClosingParenthesis() targeting DoctrineElastic\Query\Qu...ondClosingParenthesis() can also be of type null; however, DoctrineElastic\Query\Qu...arser::isMathOperator() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1681
                        // SUM(u.id) + COUNT(u.id)
1682
                        $expression = $this->ScalarExpression();
1683
                        break;
1684
1685
                    case ($this->isAggregateFunction($lookaheadType)):
1686
                        // COUNT(u.id)
1687
                        $expression = $this->AggregateExpression();
1688
                        break;
1689
1690
                    default:
1691
                        // IDENTITY(u)
1692
                        $expression = $this->FunctionDeclaration();
1693
                        break;
1694
                }
1695
1696
                break;
1697
1698
            // PartialObjectExpression (PARTIAL u.{id, name})
1699
            case ($lookaheadType === Lexer::T_PARTIAL):
1700
                $expression = $this->PartialObjectExpression();
1701
                $identVariable = $expression->identificationVariable;
1702
                break;
1703
1704
            // Subselect
1705
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS && $peek['type'] === Lexer::T_SELECT):
1706
                $this->match(Lexer::T_OPEN_PARENTHESIS);
1707
                $expression = $this->Subselect();
1708
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
1709
                break;
1710
1711
            // Shortcut: ScalarExpression => SimpleArithmeticExpression
1712
            case ($lookaheadType === Lexer::T_OPEN_PARENTHESIS):
1713
            case ($lookaheadType === Lexer::T_INTEGER):
1714
            case ($lookaheadType === Lexer::T_STRING):
1715
            case ($lookaheadType === Lexer::T_FLOAT):
1716
                // SimpleArithmeticExpression : (- u.value ) or ( + u.value )
1717
            case ($lookaheadType === Lexer::T_MINUS):
1718
            case ($lookaheadType === Lexer::T_PLUS):
1719
                $expression = $this->SimpleArithmeticExpression();
1720
                break;
1721
1722
            // NewObjectExpression (New ClassName(id, name))
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1723
            case ($lookaheadType === Lexer::T_NEW):
1724
                $expression = $this->NewObjectExpression();
1725
                break;
1726
1727
            default:
1728
                $this->syntaxError(
1729
                    'IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression',
1730
                    $this->lexer->lookahead
1731
                );
1732
        }
1733
1734
        // [["AS"] ["HIDDEN"] AliasResultVariable]
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

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

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

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

Loading history...
1735
1736
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1737
            $this->match(Lexer::T_AS);
1738
        }
1739
1740
        $hiddenAliasResultVariable = false;
1741
1742
        if ($this->lexer->isNextToken(Lexer::T_HIDDEN)) {
1743
            $this->match(Lexer::T_HIDDEN);
1744
1745
            $hiddenAliasResultVariable = true;
1746
        }
1747
1748
        $aliasResultVariable = null;
1749
1750 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1751
            $token = $this->lexer->lookahead;
1752
            $aliasResultVariable = $this->AliasResultVariable();
1753
1754
            // Include AliasResultVariable in query components.
1755
            $this->queryComponents[$aliasResultVariable] = array(
1756
                'resultVariable' => $expression,
1757
                'nestingLevel' => $this->nestingLevel,
1758
                'token' => $token,
1759
            );
1760
        }
1761
1762
        // AST
1763
1764
        $expr = new AST\SelectExpression($expression, $aliasResultVariable, $hiddenAliasResultVariable);
1765
1766
        if ($identVariable) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $identVariable of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

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

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

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

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1767
            $this->identVariableExpressions[$identVariable] = $expr;
1768
        }
1769
1770
        return $expr;
1771
    }
1772
1773
    /**
1774
     * SimpleSelectExpression ::= (
1775
     *      StateFieldPathExpression | IdentificationVariable | FunctionDeclaration |
1776
     *      AggregateExpression | "(" Subselect ")" | ScalarExpression
1777
     * ) [["AS"] AliasResultVariable]
1778
     *
1779
     * @return \Doctrine\ORM\Query\AST\SimpleSelectExpression
1780
     */
1781
    public function SimpleSelectExpression() {
1782
        $peek = $this->lexer->glimpse();
1783
1784
        switch ($this->lexer->lookahead['type']) {
1785
            case Lexer::T_IDENTIFIER:
1786
                switch (true) {
1787
                    case ($peek['type'] === Lexer::T_DOT):
1788
                        $expression = $this->StateFieldPathExpression();
1789
1790
                        return new AST\SimpleSelectExpression($expression);
1791
1792
                    case ($peek['type'] !== Lexer::T_OPEN_PARENTHESIS):
1793
                        $expression = $this->IdentificationVariable();
1794
1795
                        return new AST\SimpleSelectExpression($expression);
0 ignored issues
show
Documentation introduced by
$expression is of type string, but the function expects a object<Doctrine\ORM\Query\AST\Node>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1796
1797
                    case ($this->isFunction()):
1798
                        // SUM(u.id) + COUNT(u.id)
1799
                        if ($this->isMathOperator($this->peekBeyondClosingParenthesis())) {
0 ignored issues
show
Bug introduced by
It seems like $this->peekBeyondClosingParenthesis() targeting DoctrineElastic\Query\Qu...ondClosingParenthesis() can also be of type null; however, DoctrineElastic\Query\Qu...arser::isMathOperator() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1800
                            return new AST\SimpleSelectExpression($this->ScalarExpression());
0 ignored issues
show
Bug introduced by
It seems like $this->ScalarExpression() targeting DoctrineElastic\Query\Qu...ser::ScalarExpression() can also be of type null; however, Doctrine\ORM\Query\AST\S...pression::__construct() does only seem to accept object<Doctrine\ORM\Query\AST\Node>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1801
                        }
1802
                        // COUNT(u.id)
1803
                        if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
1804
                            return new AST\SimpleSelectExpression($this->AggregateExpression());
1805
                        }
1806
                        // IDENTITY(u)
1807
                        return new AST\SimpleSelectExpression($this->FunctionDeclaration());
0 ignored issues
show
Bug introduced by
It seems like $this->FunctionDeclaration() can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1808
1809
                    default:
1810
                        // Do nothing
1811
                }
1812
                break;
1813
1814
            case Lexer::T_OPEN_PARENTHESIS:
1815
                if ($peek['type'] !== Lexer::T_SELECT) {
1816
                    // Shortcut: ScalarExpression => SimpleArithmeticExpression
1817
                    $expression = $this->SimpleArithmeticExpression();
1818
1819
                    return new AST\SimpleSelectExpression($expression);
0 ignored issues
show
Bug introduced by
It seems like $expression defined by $this->SimpleArithmeticExpression() on line 1817 can be null; however, Doctrine\ORM\Query\AST\S...pression::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1820
                }
1821
1822
                // Subselect
1823
                $this->match(Lexer::T_OPEN_PARENTHESIS);
1824
                $expression = $this->Subselect();
1825
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
1826
1827
                return new AST\SimpleSelectExpression($expression);
1828
1829
            default:
1830
                // Do nothing
1831
        }
1832
1833
        $this->lexer->peek();
1834
1835
        $expression = $this->ScalarExpression();
1836
        $expr = new AST\SimpleSelectExpression($expression);
0 ignored issues
show
Bug introduced by
It seems like $expression defined by $this->ScalarExpression() on line 1835 can also be of type null; however, Doctrine\ORM\Query\AST\S...pression::__construct() does only seem to accept object<Doctrine\ORM\Query\AST\Node>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1837
1838
        if ($this->lexer->isNextToken(Lexer::T_AS)) {
1839
            $this->match(Lexer::T_AS);
1840
        }
1841
1842 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1843
            $token = $this->lexer->lookahead;
1844
            $resultVariable = $this->AliasResultVariable();
1845
            $expr->fieldIdentificationVariable = $resultVariable;
1846
1847
            // Include AliasResultVariable in query components.
1848
            $this->queryComponents[$resultVariable] = array(
1849
                'resultvariable' => $expr,
1850
                'nestingLevel' => $this->nestingLevel,
1851
                'token' => $token,
1852
            );
1853
        }
1854
1855
        return $expr;
1856
    }
1857
1858
    /**
1859
     * ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}*
1860
     *
1861
     * @return \Doctrine\ORM\Query\AST\ConditionalExpression
1862
     */
1863 View Code Duplication
    public function ConditionalExpression() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1864
        $conditionalTerms = array();
1865
        $conditionalTerms[] = $this->ConditionalTerm();
1866
1867
        while ($this->lexer->isNextToken(Lexer::T_OR)) {
1868
            $this->match(Lexer::T_OR);
1869
1870
            $conditionalTerms[] = $this->ConditionalTerm();
1871
        }
1872
1873
        // Phase 1 AST optimization: Prevent AST\ConditionalExpression
1874
        // if only one AST\ConditionalTerm is defined
1875
        if (count($conditionalTerms) == 1) {
1876
            return $conditionalTerms[0];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $conditionalTerms[0]; (Doctrine\ORM\Query\AST\ConditionalTerm) is incompatible with the return type documented by DoctrineElastic\Query\Qu...::ConditionalExpression of type Doctrine\ORM\Query\AST\ConditionalExpression.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1877
        }
1878
1879
        return new AST\ConditionalExpression($conditionalTerms);
1880
    }
1881
1882
    /**
1883
     * ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}*
1884
     *
1885
     * @return \Doctrine\ORM\Query\AST\ConditionalTerm
1886
     */
1887 View Code Duplication
    public function ConditionalTerm() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1888
        $conditionalFactors = array();
1889
        $conditionalFactors[] = $this->ConditionalFactor();
1890
1891
        while ($this->lexer->isNextToken(Lexer::T_AND)) {
1892
            $this->match(Lexer::T_AND);
1893
1894
            $conditionalFactors[] = $this->ConditionalFactor();
1895
        }
1896
1897
        // Phase 1 AST optimization: Prevent AST\ConditionalTerm
1898
        // if only one AST\ConditionalFactor is defined
1899
        if (count($conditionalFactors) == 1) {
1900
            return $conditionalFactors[0];
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $conditionalFactors[0]; (Doctrine\ORM\Query\AST\C...y\AST\ConditionalFactor) is incompatible with the return type documented by DoctrineElastic\Query\QueryParser::ConditionalTerm of type Doctrine\ORM\Query\AST\ConditionalTerm.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
1901
        }
1902
1903
        return new AST\ConditionalTerm($conditionalFactors);
1904
    }
1905
1906
    public function ConditionalFactor() {
1907
        $not = false;
1908
1909
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
1910
            $this->match(Lexer::T_NOT);
1911
1912
            $not = true;
1913
        }
1914
1915
        $conditionalPrimary = $this->ConditionalPrimary();
1916
1917
        // Phase 1 AST optimization: Prevent AST\ConditionalFactor
1918
        // if only one AST\ConditionalPrimary is defined
1919
        if (!$not) {
1920
            return $conditionalPrimary;
1921
        }
1922
1923
        $conditionalFactor = new AST\ConditionalFactor($conditionalPrimary);
1924
        $conditionalFactor->not = $not;
1925
1926
        return $conditionalFactor;
1927
    }
1928
1929
    /**
1930
     * ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")"
1931
     *
1932
     * @return \Doctrine\ORM\Query\AST\ConditionalPrimary
1933
     */
1934
    public function ConditionalPrimary() {
1935
        $condPrimary = new AST\ConditionalPrimary;
1936
1937
        if (!$this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
1938
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
1939
1940
            return $condPrimary;
1941
        }
1942
1943
        // Peek beyond the matching closing parenthesis ')'
1944
        $peek = $this->peekBeyondClosingParenthesis();
1945
1946
        if (in_array($peek['value'], array("=", "<", "<=", "<>", ">", ">=", "!=")) ||
1947
            in_array($peek['type'], array(Lexer::T_NOT, Lexer::T_BETWEEN, Lexer::T_LIKE, Lexer::T_IN, Lexer::T_IS, Lexer::T_EXISTS)) ||
1948
            $this->isMathOperator($peek)
1949
        ) {
1950
            $condPrimary->simpleConditionalExpression = $this->SimpleConditionalExpression();
1951
1952
            return $condPrimary;
1953
        }
1954
1955
        $this->match(Lexer::T_OPEN_PARENTHESIS);
1956
        $condPrimary->conditionalExpression = $this->ConditionalExpression();
1957
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
1958
1959
        return $condPrimary;
1960
    }
1961
1962
    /**
1963
     * SimpleConditionalExpression ::=
1964
     *      ComparisonExpression | BetweenExpression | LikeExpression |
1965
     *      InExpression | NullComparisonExpression | ExistsExpression |
1966
     *      EmptyCollectionComparisonExpression | CollectionMemberExpression |
1967
     *      InstanceOfExpression
1968
     */
1969
    public function SimpleConditionalExpression() {
1970
        if ($this->lexer->isNextToken(Lexer::T_EXISTS)) {
1971
            return $this->ExistsExpression();
1972
        }
1973
1974
        $token = $this->lexer->lookahead;
1975
        $peek = $this->lexer->glimpse();
1976
        $lookahead = $token;
1977
1978
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
1979
            $token = $this->lexer->glimpse();
1980
        }
1981
1982
        if ($token['type'] === Lexer::T_IDENTIFIER || $token['type'] === Lexer::T_INPUT_PARAMETER || $this->isFunction()) {
1983
            // Peek beyond the matching closing parenthesis.
1984
            $beyond = $this->lexer->peek();
1985
1986
            switch ($peek['value']) {
1987
                case '(':
1988
                    // Peeks beyond the matched closing parenthesis.
1989
                    $token = $this->peekBeyondClosingParenthesis(false);
1990
1991
                    if ($token['type'] === Lexer::T_NOT) {
1992
                        $token = $this->lexer->peek();
1993
                    }
1994
1995
                    if ($token['type'] === Lexer::T_IS) {
1996
                        $lookahead = $this->lexer->peek();
1997
                    }
1998
                    break;
1999
2000
                default:
2001
                    // Peek beyond the PathExpression or InputParameter.
2002
                    $token = $beyond;
2003
2004
                    while ($token['value'] === '.') {
2005
                        $this->lexer->peek();
2006
2007
                        $token = $this->lexer->peek();
2008
                    }
2009
2010
                    // Also peek beyond a NOT if there is one.
2011
                    if ($token['type'] === Lexer::T_NOT) {
2012
                        $token = $this->lexer->peek();
2013
                    }
2014
2015
                    // We need to go even further in case of IS (differentiate between NULL and EMPTY)
2016
                    $lookahead = $this->lexer->peek();
2017
            }
2018
2019
            // Also peek beyond a NOT if there is one.
2020
            if ($lookahead['type'] === Lexer::T_NOT) {
2021
                $lookahead = $this->lexer->peek();
2022
            }
2023
2024
            $this->lexer->resetPeek();
2025
        }
2026
2027
        if ($token['type'] === Lexer::T_BETWEEN) {
2028
            return $this->BetweenExpression();
2029
        }
2030
2031
        if ($token['type'] === Lexer::T_LIKE) {
2032
            return $this->LikeExpression();
2033
        }
2034
2035
        if ($token['type'] === Lexer::T_IN) {
2036
            return $this->InExpression();
2037
        }
2038
2039
        if ($token['type'] === Lexer::T_INSTANCE) {
2040
            return $this->InstanceOfExpression();
2041
        }
2042
2043
        if ($token['type'] === Lexer::T_MEMBER) {
2044
            return $this->CollectionMemberExpression();
2045
        }
2046
2047
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_NULL) {
2048
            return $this->NullComparisonExpression();
2049
        }
2050
2051
        if ($token['type'] === Lexer::T_IS && $lookahead['type'] === Lexer::T_EMPTY) {
2052
            return $this->EmptyCollectionComparisonExpression();
2053
        }
2054
2055
        return $this->ComparisonExpression();
2056
    }
2057
2058
    /**
2059
     * EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY"
2060
     *
2061
     * @return \Doctrine\ORM\Query\AST\EmptyCollectionComparisonExpression
2062
     */
2063 View Code Duplication
    public function EmptyCollectionComparisonExpression() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2064
        $emptyCollectionCompExpr = new AST\EmptyCollectionComparisonExpression(
2065
            $this->CollectionValuedPathExpression()
2066
        );
2067
        $this->match(Lexer::T_IS);
2068
2069
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2070
            $this->match(Lexer::T_NOT);
2071
            $emptyCollectionCompExpr->not = true;
2072
        }
2073
2074
        $this->match(Lexer::T_EMPTY);
2075
2076
        return $emptyCollectionCompExpr;
2077
    }
2078
2079
    /**
2080
     * CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression
2081
     *
2082
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2083
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2084
     *
2085
     * @return \Doctrine\ORM\Query\AST\CollectionMemberExpression
2086
     */
2087 View Code Duplication
    public function CollectionMemberExpression() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2088
        $not = false;
2089
        $entityExpr = $this->EntityExpression();
2090
2091
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2092
            $this->match(Lexer::T_NOT);
2093
2094
            $not = true;
2095
        }
2096
2097
        $this->match(Lexer::T_MEMBER);
2098
2099
        if ($this->lexer->isNextToken(Lexer::T_OF)) {
2100
            $this->match(Lexer::T_OF);
2101
        }
2102
2103
        $collMemberExpr = new AST\CollectionMemberExpression(
2104
            $entityExpr, $this->CollectionValuedPathExpression()
2105
        );
2106
        $collMemberExpr->not = $not;
2107
2108
        return $collMemberExpr;
2109
    }
2110
2111
    /**
2112
     * Literal ::= string | char | integer | float | boolean
2113
     *
2114
     * @return \Doctrine\ORM\Query\AST\Literal
2115
     */
2116
    public function Literal() {
2117
        switch ($this->lexer->lookahead['type']) {
2118 View Code Duplication
            case Lexer::T_STRING:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2119
                $this->match(Lexer::T_STRING);
2120
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2121
2122
            case Lexer::T_INTEGER:
2123 View Code Duplication
            case Lexer::T_FLOAT:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2124
                $this->match(
2125
                    $this->lexer->isNextToken(Lexer::T_INTEGER) ? Lexer::T_INTEGER : Lexer::T_FLOAT
2126
                );
2127
                return new AST\Literal(AST\Literal::NUMERIC, $this->lexer->token['value']);
2128
2129
            case Lexer::T_TRUE:
2130 View Code Duplication
            case Lexer::T_FALSE:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2131
                $this->match(
2132
                    $this->lexer->isNextToken(Lexer::T_TRUE) ? Lexer::T_TRUE : Lexer::T_FALSE
2133
                );
2134
                return new AST\Literal(AST\Literal::BOOLEAN, $this->lexer->token['value']);
2135
2136
            default:
2137
                $this->syntaxError('Literal');
2138
        }
2139
    }
2140
2141
    /**
2142
     * InParameter ::= Literal | InputParameter
2143
     *
2144
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2145
     */
2146
    public function InParameter() {
2147
        if ($this->lexer->lookahead['type'] == Lexer::T_INPUT_PARAMETER) {
2148
            return $this->InputParameter();
2149
        }
2150
2151
        return $this->Literal();
2152
    }
2153
2154
    /**
2155
     * InputParameter ::= PositionalParameter | NamedParameter
2156
     *
2157
     * @return \Doctrine\ORM\Query\AST\InputParameter
2158
     */
2159
    public function InputParameter() {
2160
        $this->match(Lexer::T_INPUT_PARAMETER);
2161
2162
        return new AST\InputParameter($this->lexer->token['value']);
2163
    }
2164
2165
    /**
2166
     * ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")"
2167
     *
2168
     * @return \Doctrine\ORM\Query\AST\ArithmeticExpression
2169
     */
2170
    public function ArithmeticExpression() {
2171
        $expr = new AST\ArithmeticExpression;
2172
2173
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2174
            $peek = $this->lexer->glimpse();
2175
2176
            if ($peek['type'] === Lexer::T_SELECT) {
2177
                $this->match(Lexer::T_OPEN_PARENTHESIS);
2178
                $expr->subselect = $this->Subselect();
2179
                $this->match(Lexer::T_CLOSE_PARENTHESIS);
2180
2181
                return $expr;
2182
            }
2183
        }
2184
2185
        $expr->simpleArithmeticExpression = $this->SimpleArithmeticExpression();
2186
2187
        return $expr;
2188
    }
2189
2190
    /**
2191
     * SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}*
2192
     *
2193
     * @return \Doctrine\ORM\Query\AST\SimpleArithmeticExpression
2194
     */
2195 View Code Duplication
    public function SimpleArithmeticExpression() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2196
        $terms = array();
2197
        $terms[] = $this->ArithmeticTerm();
2198
2199
        while (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2200
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2201
2202
            $terms[] = $this->lexer->token['value'];
2203
            $terms[] = $this->ArithmeticTerm();
2204
        }
2205
2206
        // Phase 1 AST optimization: Prevent AST\SimpleArithmeticExpression
2207
        // if only one AST\ArithmeticTerm is defined
2208
        if (count($terms) == 1) {
2209
            return $terms[0];
0 ignored issues
show
Bug Compatibility introduced by
The expression $terms[0]; of type Doctrine\ORM\Query\AST\ArithmeticTerm|null adds the type Doctrine\ORM\Query\AST\ArithmeticTerm to the return on line 2209 which is incompatible with the return type documented by DoctrineElastic\Query\Qu...pleArithmeticExpression of type Doctrine\ORM\Query\AST\S...ithmeticExpression|null.
Loading history...
2210
        }
2211
2212
        return new AST\SimpleArithmeticExpression($terms);
2213
    }
2214
2215
    /**
2216
     * ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}*
2217
     *
2218
     * @return \Doctrine\ORM\Query\AST\ArithmeticTerm
2219
     */
2220 View Code Duplication
    public function ArithmeticTerm() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2221
        $factors = array();
2222
        $factors[] = $this->ArithmeticFactor();
2223
2224
        while (($isMult = $this->lexer->isNextToken(Lexer::T_MULTIPLY)) || $this->lexer->isNextToken(Lexer::T_DIVIDE)) {
2225
            $this->match(($isMult) ? Lexer::T_MULTIPLY : Lexer::T_DIVIDE);
2226
2227
            $factors[] = $this->lexer->token['value'];
2228
            $factors[] = $this->ArithmeticFactor();
2229
        }
2230
2231
        // Phase 1 AST optimization: Prevent AST\ArithmeticTerm
2232
        // if only one AST\ArithmeticFactor is defined
2233
        if (count($factors) == 1) {
2234
            return $factors[0];
0 ignored issues
show
Bug Compatibility introduced by
The expression $factors[0]; of type Doctrine\ORM\Query\AST\ArithmeticFactor|null adds the type Doctrine\ORM\Query\AST\ArithmeticFactor to the return on line 2234 which is incompatible with the return type documented by DoctrineElastic\Query\QueryParser::ArithmeticTerm of type Doctrine\ORM\Query\AST\ArithmeticTerm|null.
Loading history...
2235
        }
2236
2237
        return new AST\ArithmeticTerm($factors);
2238
    }
2239
2240
    /**
2241
     * ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary
2242
     *
2243
     * @return \Doctrine\ORM\Query\AST\ArithmeticFactor
2244
     */
2245
    public function ArithmeticFactor() {
2246
        $sign = null;
2247
2248
        if (($isPlus = $this->lexer->isNextToken(Lexer::T_PLUS)) || $this->lexer->isNextToken(Lexer::T_MINUS)) {
2249
            $this->match(($isPlus) ? Lexer::T_PLUS : Lexer::T_MINUS);
2250
            $sign = $isPlus;
2251
        }
2252
2253
        $primary = $this->ArithmeticPrimary();
2254
2255
        // Phase 1 AST optimization: Prevent AST\ArithmeticFactor
2256
        // if only one AST\ArithmeticPrimary is defined
2257
        if ($sign === null) {
2258
            return $primary;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $primary; (Doctrine\ORM\Query\AST\P...e\ORM\Query\AST\Literal) is incompatible with the return type documented by DoctrineElastic\Query\Qu...arser::ArithmeticFactor of type Doctrine\ORM\Query\AST\ArithmeticFactor|null.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
2259
        }
2260
2261
        return new AST\ArithmeticFactor($primary, $sign);
2262
    }
2263
2264
    /**
2265
     * ArithmeticPrimary ::= SingleValuedPathExpression | Literal | ParenthesisExpression
2266
     *          | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings
2267
     *          | FunctionsReturningDatetime | IdentificationVariable | ResultVariable
2268
     *          | InputParameter | CaseExpression
2269
     */
2270
    public function ArithmeticPrimary() {
2271 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2272
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2273
2274
            $expr = $this->SimpleArithmeticExpression();
2275
2276
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2277
2278
            return new AST\ParenthesisExpression($expr);
0 ignored issues
show
Bug introduced by
It seems like $expr defined by $this->SimpleArithmeticExpression() on line 2274 can be null; however, Doctrine\ORM\Query\AST\P...pression::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2279
        }
2280
2281
        switch ($this->lexer->lookahead['type']) {
2282
            case Lexer::T_COALESCE:
2283
            case Lexer::T_NULLIF:
2284
            case Lexer::T_CASE:
2285
                return $this->CaseExpression();
2286
2287
            case Lexer::T_IDENTIFIER:
2288
                $peek = $this->lexer->glimpse();
2289
2290
                if ($peek['value'] == '(') {
2291
                    return $this->FunctionDeclaration();
2292
                }
2293
2294
                if ($peek['value'] == '.') {
2295
                    return $this->SingleValuedPathExpression();
2296
                }
2297
2298
                if (isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])) {
2299
                    return $this->ResultVariable();
2300
                }
2301
2302
                return $this->StateFieldPathExpression();
2303
2304
            case Lexer::T_INPUT_PARAMETER:
2305
                return $this->InputParameter();
2306
2307
            default:
2308
                $peek = $this->lexer->glimpse();
2309
2310
                if ($peek['value'] == '(') {
2311
                    if ($this->isAggregateFunction($this->lexer->lookahead['type'])) {
2312
                        return $this->AggregateExpression();
2313
                    }
2314
2315
                    return $this->FunctionDeclaration();
2316
                }
2317
2318
                return $this->Literal();
2319
        }
2320
    }
2321
2322
    /**
2323
     * StringExpression ::= StringPrimary | ResultVariable | "(" Subselect ")"
2324
     *
2325
     */
2326
    public function StringExpression() {
2327
        $peek = $this->lexer->glimpse();
2328
2329
        // Subselect
2330 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS) && $peek['type'] === Lexer::T_SELECT) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2331
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2332
            $expr = $this->Subselect();
2333
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2334
2335
            return $expr;
2336
        }
2337
2338
        // ResultVariable (string)
2339
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) &&
2340
            isset($this->queryComponents[$this->lexer->lookahead['value']]['resultVariable'])
2341
        ) {
2342
            return $this->ResultVariable();
2343
        }
2344
2345
        return $this->StringPrimary();
2346
    }
2347
2348
    /**
2349
     * StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression
2350
     */
2351
    public function StringPrimary() {
2352
        $lookaheadType = $this->lexer->lookahead['type'];
2353
2354
        switch ($lookaheadType) {
2355
            case Lexer::T_IDENTIFIER:
2356
                $peek = $this->lexer->glimpse();
2357
2358
                if ($peek['value'] == '.') {
2359
                    return $this->StateFieldPathExpression();
2360
                }
2361
2362
                if ($peek['value'] == '(') {
2363
                    // do NOT directly go to FunctionsReturningString() because it doesn't check for custom functions.
2364
                    return $this->FunctionDeclaration();
2365
                }
2366
2367
                $this->syntaxError("'.' or '('");
2368
                break;
2369
2370 View Code Duplication
            case Lexer::T_STRING:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2371
                $this->match(Lexer::T_STRING);
2372
2373
                return new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2374
2375
            case Lexer::T_INPUT_PARAMETER:
2376
                return $this->InputParameter();
2377
2378
            case Lexer::T_CASE:
2379
            case Lexer::T_COALESCE:
2380
            case Lexer::T_NULLIF:
2381
                return $this->CaseExpression();
2382
2383
            default:
2384
                if ($this->isAggregateFunction($lookaheadType)) {
2385
                    return $this->AggregateExpression();
2386
                }
2387
        }
2388
2389
        $this->syntaxError(
2390
            'StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression'
2391
        );
2392
    }
2393
2394
    /**
2395
     * EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression
2396
     *
2397
     * @return \Doctrine\ORM\Query\AST\SingleValuedAssociationPathExpression |
2398
     *         \Doctrine\ORM\Query\AST\SimpleEntityExpression
2399
     */
2400
    public function EntityExpression() {
2401
        $glimpse = $this->lexer->glimpse();
2402
2403
        if ($this->lexer->isNextToken(Lexer::T_IDENTIFIER) && $glimpse['value'] === '.') {
2404
            return $this->SingleValuedAssociationPathExpression();
2405
        }
2406
2407
        return $this->SimpleEntityExpression();
2408
    }
2409
2410
    /**
2411
     * SimpleEntityExpression ::= IdentificationVariable | InputParameter
2412
     *
2413
     * @return string | \Doctrine\ORM\Query\AST\InputParameter
2414
     */
2415
    public function SimpleEntityExpression() {
2416
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2417
            return $this->InputParameter();
2418
        }
2419
2420
        return $this->StateFieldPathExpression();
2421
    }
2422
2423
    /**
2424
     * AggregateExpression ::=
2425
     *  ("AVG" | "MAX" | "MIN" | "SUM" | "COUNT") "(" ["DISTINCT"] SimpleArithmeticExpression ")"
2426
     *
2427
     * @return \Doctrine\ORM\Query\AST\AggregateExpression
2428
     */
2429
    public function AggregateExpression() {
2430
        $lookaheadType = $this->lexer->lookahead['type'];
2431
        $isDistinct = false;
2432
2433
        if (!in_array($lookaheadType, array(Lexer::T_COUNT, Lexer::T_AVG, Lexer::T_MAX, Lexer::T_MIN, Lexer::T_SUM))) {
2434
            $this->syntaxError('One of: MAX, MIN, AVG, SUM, COUNT');
2435
        }
2436
2437
        $this->match($lookaheadType);
2438
        $functionName = $this->lexer->token['value'];
2439
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2440
2441
        if ($this->lexer->isNextToken(Lexer::T_DISTINCT)) {
2442
            $this->match(Lexer::T_DISTINCT);
2443
            $isDistinct = true;
2444
        }
2445
2446
        $pathExp = $this->SimpleArithmeticExpression();
2447
2448
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2449
2450
        return new AST\AggregateExpression($functionName, $pathExp, $isDistinct);
0 ignored issues
show
Bug introduced by
It seems like $pathExp defined by $this->SimpleArithmeticExpression() on line 2446 can be null; however, Doctrine\ORM\Query\AST\A...pression::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
2451
    }
2452
2453
    /**
2454
     * QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")"
2455
     *
2456
     * @return \Doctrine\ORM\Query\AST\QuantifiedExpression
2457
     */
2458
    public function QuantifiedExpression() {
2459
        $lookaheadType = $this->lexer->lookahead['type'];
2460
        $value = $this->lexer->lookahead['value'];
2461
2462
        if (!in_array($lookaheadType, array(Lexer::T_ALL, Lexer::T_ANY, Lexer::T_SOME))) {
2463
            $this->syntaxError('ALL, ANY or SOME');
2464
        }
2465
2466
        $this->match($lookaheadType);
2467
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2468
2469
        $qExpr = new AST\QuantifiedExpression($this->Subselect());
2470
        $qExpr->type = $value;
2471
2472
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2473
2474
        return $qExpr;
2475
    }
2476
2477
    /**
2478
     * BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression
2479
     *
2480
     * @return \Doctrine\ORM\Query\AST\BetweenExpression
2481
     */
2482
    public function BetweenExpression() {
2483
        $not = false;
2484
        $arithExpr1 = $this->ArithmeticExpression();
2485
2486
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2487
            $this->match(Lexer::T_NOT);
2488
            $not = true;
2489
        }
2490
2491
        $this->match(Lexer::T_BETWEEN);
2492
        $arithExpr2 = $this->ArithmeticExpression();
2493
        $this->match(Lexer::T_AND);
2494
        $arithExpr3 = $this->ArithmeticExpression();
2495
2496
        $betweenExpr = new AST\BetweenExpression($arithExpr1, $arithExpr2, $arithExpr3);
2497
        $betweenExpr->not = $not;
2498
2499
        return $betweenExpr;
2500
    }
2501
2502
    /**
2503
     * ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression )
2504
     *
2505
     * @return \Doctrine\ORM\Query\AST\ComparisonExpression
2506
     */
2507
    public function ComparisonExpression() {
2508
        $this->lexer->glimpse();
2509
2510
        $leftExpr = $this->ArithmeticExpression();
2511
        $operator = $this->ComparisonOperator();
2512
        $rightExpr = ($this->isNextAllAnySome())
2513
            ? $this->QuantifiedExpression()
2514
            : $this->ArithmeticExpression();
2515
2516
        return new AST\ComparisonExpression($leftExpr, $operator, $rightExpr);
2517
    }
2518
2519
    /**
2520
     * InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")"
2521
     *
2522
     * @return \Doctrine\ORM\Query\AST\InExpression
2523
     */
2524
    public function InExpression() {
2525
        $inExpression = new AST\InExpression($this->ArithmeticExpression());
2526
2527
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2528
            $this->match(Lexer::T_NOT);
2529
            $inExpression->not = true;
2530
        }
2531
2532
        $this->match(Lexer::T_IN);
2533
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2534
2535
        if ($this->lexer->isNextToken(Lexer::T_SELECT)) {
2536
            $inExpression->subselect = $this->Subselect();
2537
        } else {
2538
            $literals = array();
2539
            $literals[] = $this->InParameter();
2540
2541
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2542
                $this->match(Lexer::T_COMMA);
2543
                $literals[] = $this->InParameter();
2544
            }
2545
2546
            $inExpression->literals = $literals;
2547
        }
2548
2549
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2550
2551
        return $inExpression;
2552
    }
2553
2554
    /**
2555
     * InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")")
2556
     *
2557
     * @return \Doctrine\ORM\Query\AST\InstanceOfExpression
2558
     */
2559
    public function InstanceOfExpression() {
2560
        $instanceOfExpression = new AST\InstanceOfExpression($this->IdentificationVariable());
2561
2562
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2563
            $this->match(Lexer::T_NOT);
2564
            $instanceOfExpression->not = true;
2565
        }
2566
2567
        $this->match(Lexer::T_INSTANCE);
2568
        $this->match(Lexer::T_OF);
2569
2570
        $exprValues = array();
2571
2572
        if ($this->lexer->isNextToken(Lexer::T_OPEN_PARENTHESIS)) {
2573
            $this->match(Lexer::T_OPEN_PARENTHESIS);
2574
2575
            $exprValues[] = $this->InstanceOfParameter();
2576
2577
            while ($this->lexer->isNextToken(Lexer::T_COMMA)) {
2578
                $this->match(Lexer::T_COMMA);
2579
2580
                $exprValues[] = $this->InstanceOfParameter();
2581
            }
2582
2583
            $this->match(Lexer::T_CLOSE_PARENTHESIS);
2584
2585
            $instanceOfExpression->value = $exprValues;
2586
2587
            return $instanceOfExpression;
2588
        }
2589
2590
        $exprValues[] = $this->InstanceOfParameter();
2591
2592
        $instanceOfExpression->value = $exprValues;
2593
2594
        return $instanceOfExpression;
2595
    }
2596
2597
    /**
2598
     * InstanceOfParameter ::= AbstractSchemaName | InputParameter
2599
     *
2600
     * @return mixed
2601
     */
2602
    public function InstanceOfParameter() {
2603 View Code Duplication
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2604
            $this->match(Lexer::T_INPUT_PARAMETER);
2605
2606
            return new AST\InputParameter($this->lexer->token['value']);
2607
        }
2608
2609
        return $this->AliasIdentificationVariable();
2610
    }
2611
2612
    /**
2613
     * LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char]
2614
     *
2615
     * @return \Doctrine\ORM\Query\AST\LikeExpression
2616
     */
2617
    public function LikeExpression() {
2618
        $stringExpr = $this->StringExpression();
2619
        $not = false;
2620
2621
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2622
            $this->match(Lexer::T_NOT);
2623
            $not = true;
2624
        }
2625
2626
        $this->match(Lexer::T_LIKE);
2627
2628
        if ($this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER)) {
2629
            $this->match(Lexer::T_INPUT_PARAMETER);
2630
            $stringPattern = new AST\InputParameter($this->lexer->token['value']);
2631
        } else {
2632
            $stringPattern = $this->StringPrimary();
2633
        }
2634
2635
        $escapeChar = null;
2636
2637
        if ($this->lexer->lookahead['type'] === Lexer::T_ESCAPE) {
2638
            $this->match(Lexer::T_ESCAPE);
2639
            $this->match(Lexer::T_STRING);
2640
2641
            $escapeChar = new AST\Literal(AST\Literal::STRING, $this->lexer->token['value']);
2642
        }
2643
2644
        $likeExpr = new AST\LikeExpression($stringExpr, $stringPattern, $escapeChar);
0 ignored issues
show
Bug introduced by
It seems like $stringExpr defined by $this->StringExpression() on line 2618 can also be of type null or string; however, Doctrine\ORM\Query\AST\L...pression::__construct() does only seem to accept object<Doctrine\ORM\Query\AST\Node>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $stringPattern defined by $this->StringPrimary() on line 2632 can also be of type null or object<Doctrine\ORM\Quer...ST\AggregateExpression> or object<Doctrine\ORM\Query\AST\CoalesceExpression> or object<Doctrine\ORM\Quer...Functions\FunctionNode> or object<Doctrine\ORM\Quer...\GeneralCaseExpression> or object<Doctrine\ORM\Query\AST\Literal> or object<Doctrine\ORM\Query\AST\NullIfExpression> or object<Doctrine\ORM\Query\AST\PathExpression> or object<Doctrine\ORM\Quer...T\SimpleCaseExpression>; however, Doctrine\ORM\Query\AST\L...pression::__construct() does only seem to accept object<Doctrine\ORM\Query\AST\InputParameter>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2645
        $likeExpr->not = $not;
2646
2647
        return $likeExpr;
2648
    }
2649
2650
    /**
2651
     * NullComparisonExpression ::= (InputParameter | NullIfExpression | CoalesceExpression | AggregateExpression | FunctionDeclaration | IdentificationVariable | SingleValuedPathExpression | ResultVariable) "IS" ["NOT"] "NULL"
2652
     *
2653
     * @return \Doctrine\ORM\Query\AST\NullComparisonExpression
2654
     */
2655
    public function NullComparisonExpression() {
2656
        switch (true) {
2657
            case $this->lexer->isNextToken(Lexer::T_INPUT_PARAMETER):
2658
                $this->match(Lexer::T_INPUT_PARAMETER);
2659
2660
                $expr = new AST\InputParameter($this->lexer->token['value']);
2661
                break;
2662
2663
            case $this->lexer->isNextToken(Lexer::T_NULLIF):
2664
                $expr = $this->NullIfExpression();
2665
                break;
2666
2667
            case $this->lexer->isNextToken(Lexer::T_COALESCE):
2668
                $expr = $this->CoalesceExpression();
2669
                break;
2670
2671
            case $this->isAggregateFunction($this->lexer->lookahead['type']):
2672
                $expr = $this->AggregateExpression();
2673
                break;
2674
2675
            case $this->isFunction():
2676
                $expr = $this->FunctionDeclaration();
2677
                break;
2678
2679
            default:
2680
                // We need to check if we are in a IdentificationVariable or SingleValuedPathExpression
2681
                $glimpse = $this->lexer->glimpse();
2682
2683
                if ($glimpse['type'] === Lexer::T_DOT) {
2684
                    $expr = $this->SingleValuedPathExpression();
2685
2686
                    // Leave switch statement
2687
                    break;
2688
                }
2689
2690
                $lookaheadValue = $this->lexer->lookahead['value'];
2691
2692
                // Validate existing component
2693
                if (!isset($this->queryComponents[$lookaheadValue])) {
2694
                    $this->semanticalError('Cannot add having condition on undefined result variable.');
2695
                }
2696
2697
                // Validating ResultVariable
2698
                if (!isset($this->queryComponents[$lookaheadValue]['resultVariable'])) {
2699
                    $this->semanticalError('Cannot add having condition on a non result variable.');
2700
                }
2701
2702
                $expr = $this->ResultVariable();
2703
                break;
2704
        }
2705
2706
        $nullCompExpr = new AST\NullComparisonExpression($expr);
0 ignored issues
show
Bug introduced by
It seems like $expr can also be of type null or string; however, Doctrine\ORM\Query\AST\N...pression::__construct() does only seem to accept object<Doctrine\ORM\Query\AST\Node>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2707
2708
        $this->match(Lexer::T_IS);
2709
2710
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2711
            $this->match(Lexer::T_NOT);
2712
2713
            $nullCompExpr->not = true;
2714
        }
2715
2716
        $this->match(Lexer::T_NULL);
2717
2718
        return $nullCompExpr;
2719
    }
2720
2721
    /**
2722
     * ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")"
2723
     *
2724
     * @return \Doctrine\ORM\Query\AST\ExistsExpression
2725
     */
2726 View Code Duplication
    public function ExistsExpression() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2727
        $not = false;
2728
2729
        if ($this->lexer->isNextToken(Lexer::T_NOT)) {
2730
            $this->match(Lexer::T_NOT);
2731
            $not = true;
2732
        }
2733
2734
        $this->match(Lexer::T_EXISTS);
2735
        $this->match(Lexer::T_OPEN_PARENTHESIS);
2736
2737
        $existsExpression = new AST\ExistsExpression($this->Subselect());
2738
        $existsExpression->not = $not;
2739
2740
        $this->match(Lexer::T_CLOSE_PARENTHESIS);
2741
2742
        return $existsExpression;
2743
    }
2744
2745
    /**
2746
     * ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!="
2747
     *
2748
     * @return string
2749
     */
2750
    public function ComparisonOperator() {
2751
        switch ($this->lexer->lookahead['value']) {
2752
            case '=':
2753
                $this->match(Lexer::T_EQUALS);
2754
2755
                return '=';
2756
2757
            case '<':
2758
                $this->match(Lexer::T_LOWER_THAN);
2759
                $operator = '<';
2760
2761
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
2762
                    $this->match(Lexer::T_EQUALS);
2763
                    $operator .= '=';
2764
                } else if ($this->lexer->isNextToken(Lexer::T_GREATER_THAN)) {
2765
                    $this->match(Lexer::T_GREATER_THAN);
2766
                    $operator .= '>';
2767
                }
2768
2769
                return $operator;
2770
2771
            case '>':
2772
                $this->match(Lexer::T_GREATER_THAN);
2773
                $operator = '>';
2774
2775
                if ($this->lexer->isNextToken(Lexer::T_EQUALS)) {
2776
                    $this->match(Lexer::T_EQUALS);
2777
                    $operator .= '=';
2778
                }
2779
2780
                return $operator;
2781
2782
            case '!':
2783
                $this->match(Lexer::T_NEGATE);
2784
                $this->match(Lexer::T_EQUALS);
2785
2786
                return '<>';
2787
2788
            default:
2789
                $this->syntaxError('=, <, <=, <>, >, >=, !=');
2790
        }
2791
    }
2792
2793
    /**
2794
     * FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDatetime
2795
     *
2796
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
2797
     */
2798
    public function FunctionDeclaration() {
2799
        $token = $this->lexer->lookahead;
2800
        $funcName = strtolower($token['value']);
2801
2802
        // Check for built-in functions first!
2803
        switch (true) {
2804
            case (isset(self::$_STRING_FUNCTIONS[$funcName])):
2805
                return $this->FunctionsReturningStrings();
2806
2807
            case (isset(self::$_NUMERIC_FUNCTIONS[$funcName])):
2808
                return $this->FunctionsReturningNumerics();
2809
2810
            case (isset(self::$_DATETIME_FUNCTIONS[$funcName])):
2811
                return $this->FunctionsReturningDatetime();
2812
2813
            default:
2814
                return $this->CustomFunctionDeclaration();
2815
        }
2816
    }
2817
2818
    /**
2819
     * Helper function for FunctionDeclaration grammar rule.
2820
     *
2821
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
2822
     */
2823
    private function CustomFunctionDeclaration() {
2824
        $token = $this->lexer->lookahead;
2825
        $funcName = strtolower($token['value']);
2826
2827
        // Check for custom functions afterwards
2828
        $config = $this->em->getConfiguration();
2829
2830
        switch (true) {
2831
            case ($config->getCustomStringFunction($funcName) !== null):
2832
                return $this->CustomFunctionsReturningStrings();
2833
2834
            case ($config->getCustomNumericFunction($funcName) !== null):
2835
                return $this->CustomFunctionsReturningNumerics();
2836
2837
            case ($config->getCustomDatetimeFunction($funcName) !== null):
2838
                return $this->CustomFunctionsReturningDatetime();
2839
2840
            default:
2841
                $this->syntaxError('known function', $token);
2842
        }
2843
    }
2844
2845
    /**
2846
     * FunctionsReturningNumerics ::=
2847
     *      "LENGTH" "(" StringPrimary ")" |
2848
     *      "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" |
2849
     *      "ABS" "(" SimpleArithmeticExpression ")" |
2850
     *      "SQRT" "(" SimpleArithmeticExpression ")" |
2851
     *      "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
2852
     *      "SIZE" "(" CollectionValuedPathExpression ")" |
2853
     *      "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
2854
     *      "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" |
2855
     *      "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
2856
     *
2857
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
2858
     */
2859 View Code Duplication
    public function FunctionsReturningNumerics() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2860
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
2861
        $funcClass = self::$_NUMERIC_FUNCTIONS[$funcNameLower];
2862
2863
        $function = new $funcClass($funcNameLower);
2864
        $function->parse($this);
2865
2866
        return $function;
2867
    }
2868
2869
    /**
2870
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
2871
     */
2872 View Code Duplication
    public function CustomFunctionsReturningNumerics() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2873
        // getCustomNumericFunction is case-insensitive
2874
        $functionName = strtolower($this->lexer->lookahead['value']);
2875
        $functionClass = $this->em->getConfiguration()->getCustomNumericFunction($functionName);
2876
2877
        $function = is_string($functionClass)
2878
            ? new $functionClass($functionName)
2879
            : call_user_func($functionClass, $functionName);
2880
2881
        $function->parse($this);
2882
2883
        return $function;
2884
    }
2885
2886
    /**
2887
     * FunctionsReturningDateTime ::=
2888
     *     "CURRENT_DATE" |
2889
     *     "CURRENT_TIME" |
2890
     *     "CURRENT_TIMESTAMP" |
2891
     *     "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" |
2892
     *     "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")"
2893
     *
2894
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
2895
     */
2896 View Code Duplication
    public function FunctionsReturningDatetime() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2897
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
2898
        $funcClass = self::$_DATETIME_FUNCTIONS[$funcNameLower];
2899
2900
        $function = new $funcClass($funcNameLower);
2901
        $function->parse($this);
2902
2903
        return $function;
2904
    }
2905
2906
    /**
2907
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
2908
     */
2909 View Code Duplication
    public function CustomFunctionsReturningDatetime() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2910
        // getCustomDatetimeFunction is case-insensitive
2911
        $functionName = $this->lexer->lookahead['value'];
2912
        $functionClass = $this->em->getConfiguration()->getCustomDatetimeFunction($functionName);
2913
2914
        $function = is_string($functionClass)
2915
            ? new $functionClass($functionName)
2916
            : call_user_func($functionClass, $functionName);
2917
2918
        $function->parse($this);
2919
2920
        return $function;
2921
    }
2922
2923
    /**
2924
     * FunctionsReturningStrings ::=
2925
     *   "CONCAT" "(" StringPrimary "," StringPrimary ")" |
2926
     *   "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" |
2927
     *   "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" |
2928
     *   "LOWER" "(" StringPrimary ")" |
2929
     *   "UPPER" "(" StringPrimary ")" |
2930
     *   "IDENTITY" "(" SingleValuedAssociationPathExpression {"," string} ")"
2931
     *
2932
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
2933
     */
2934 View Code Duplication
    public function FunctionsReturningStrings() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2935
        $funcNameLower = strtolower($this->lexer->lookahead['value']);
2936
        $funcClass = self::$_STRING_FUNCTIONS[$funcNameLower];
2937
2938
        $function = new $funcClass($funcNameLower);
2939
        $function->parse($this);
2940
2941
        return $function;
2942
    }
2943
2944
    /**
2945
     * @return \Doctrine\ORM\Query\AST\Functions\FunctionNode
2946
     */
2947 View Code Duplication
    public function CustomFunctionsReturningStrings() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2948
        // getCustomStringFunction is case-insensitive
2949
        $functionName = $this->lexer->lookahead['value'];
2950
        $functionClass = $this->em->getConfiguration()->getCustomStringFunction($functionName);
2951
2952
        $function = is_string($functionClass)
2953
            ? new $functionClass($functionName)
2954
            : call_user_func($functionClass, $functionName);
2955
2956
        $function->parse($this);
2957
2958
        return $function;
2959
    }
2960
}
2961