ExpressionParser::_parseAdditive()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 20
Code Lines 15

Duplication

Lines 5
Ratio 25 %

Importance

Changes 0
Metric Value
dl 5
loc 20
rs 9.2
c 0
b 0
f 0
cc 4
eloc 15
nc 3
nop 0
1
<?php
2
3
namespace POData\UriProcessor\QueryProcessor\ExpressionParser;
4
5
use POData\Common\Messages;
6
use POData\Common\ODataConstants;
7
use POData\Common\ODataException;
8
use POData\Providers\Metadata\Type\Boolean;
9
use POData\Providers\Metadata\Type\DateTime;
10
use POData\Providers\Metadata\Type\Decimal;
11
use POData\Providers\Metadata\Type\StringType;
12
use POData\Providers\Metadata\Type\Int64;
13
use POData\Providers\Metadata\Type\Int32;
14
use POData\Providers\Metadata\Type\Double;
15
use POData\Providers\Metadata\Type\Single;
16
use POData\Providers\Metadata\Type\Guid;
17
use POData\Providers\Metadata\Type\Binary;
18
use POData\Providers\Metadata\Type\Null1;
19
use POData\Providers\Metadata\Type\INavigationType;
20
use POData\Providers\Metadata\Type\TypeCode;
21
use POData\Providers\Metadata\Type\IType;
22
use POData\Providers\Metadata\ResourceType;
23
use POData\Providers\Metadata\ResourceSet;
24
use POData\Providers\Metadata\ResourcePropertyKind;
25
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\AbstractExpression;
26
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\PropertyAccessExpression;
27
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ArithmeticExpression;
28
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\RelationalExpression;
29
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\LogicalExpression;
30
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\FunctionCallExpression;
31
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\UnaryExpression;
32
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ConstantExpression;
33
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ExpressionType;
34
use POData\UriProcessor\QueryProcessor\FunctionDescription;
35
use POData\Common\NotImplementedException;
36
37
38
/**
39
 * Class ExpressionParser
40
 * @package POData\UriProcessor\QueryProcessor\ExpressionParser
41
 */
42
class ExpressionParser
43
{
44
    const RECURSION_LIMIT = 200;
45
46
    /**     
47
     * The Lexical analyzer
48
     * 
49
     * @var ExpressionLexer
50
     */
51
    private $_lexer;
52
    
53
    /**     
54
     * The current recursion depth
55
     * 
56
     * @var int
57
     */
58
    private $_recursionDepth;
59
    
60
    /**
61
     * The ResourceType on which $filter condition needs to be applied
62
     * 
63
     * @var ResourceType
64
     */
65
    private $_resourceType;
66
    
67
    /**
68
     * @var bool
69
     */
70
    private $_isPHPExpressionProvider;
71
72
    /**
73
     * True if the filter expression contains level 2 property access, for example
74
     * Customers?$filter=Address/LineNumber eq 12
75
     * Customer?$filter=Order/OrderID gt 1234    
76
     * False otherwise.
77
     * 
78
     * @var bool
79
     */
80
    private $_hasLevel2PropertyInTheExpression;
81
    
82
83
    /**
84
     * Construct a new instance of ExpressionParser
85
     * 
86
     * @param string $text The expression to parse.
87
     * @param ResourceType $resourceType The resource type of the resource targeted by the resource path.
88
     * @param bool $isPHPExpressionProvider
89
     *
90
     * TODO Expression parser should not depend on the fact that end user is implementing IExpressionProvider or not.
91
     */
92
    public function __construct($text, ResourceType $resourceType, $isPHPExpressionProvider)
93
    {
94
        $this->_lexer = new ExpressionLexer($text);
95
        $this->_resourceType = $resourceType;
96
        $this->_isPHPExpressionProvider = $isPHPExpressionProvider;
97
        $this->_hasLevel2PropertyInTheExpression = false;
98
    }
99
100
    /**
101
     * Checks whether the expression contains level 2 property access. 
102
     * 
103
     * @return boolean
104
     */
105
    public function hasLevel2Property()
106
    {
107
        return $this->_hasLevel2PropertyInTheExpression;
108
    }
109
110
    /**  
111
     * Get the current token from lexer
112
     * 
113
     * @return ExpressionToken
114
     */
115
    private function _getCurrentToken()
116
    {
117
        return $this->_lexer->getCurrentToken();
118
    }
119
120
    /** 
121
     * Set the current token in lexer
122
     * 
123
     * @param ExpressionToken $token The token to set as current token.
124
     * 
125
     * @return void
126
     */
127
    private function _setCurrentToken($token)
128
    {
129
        $this->_lexer->setCurrentToken($token);
130
    }  
131
132
    /**     
133
     * Resets parser with new expression string.
134
     * 
135
     * @param string $text Reset the expression to parse.
136
     * 
137
     * @return void
138
     */
139
    public function resetParser($text)
140
    {
141
        $this->_lexer = new ExpressionLexer($text);
142
        $this->_recursionDepth = 0;
143
    }
144
145
    /** 
146
     * Parse the expression in filter option.
147
     * 
148
     * @return AbstractExpression
149
     */
150
    public function parseFilter()
151
    {
152
        return $this->_parseExpression();
153
    }
154
155
    /**
156
     * Start parsing the expression
157
     * 
158
     * @return AbstractExpression
159
     */
160
    private function _parseExpression()
161
    {
162
        $this->_recurseEnter();
163
        $expr = $this->_parseLogicalOr();
164
        $this->_recurseLeave();
165
        return $expr;
166
    }
167
168
    /** 
169
     * Parse logical or (or)
170
     * 
171
     * @return AbstractExpression
172
     */
173 View Code Duplication
    private function _parseLogicalOr()
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...
174
    {
175
        $this->_recurseEnter();
176
        $left = $this->_parseLogicalAnd();
177
        while ($this->_tokenIdentifierIs(ODataConstants::KEYWORD_OR)) {
178
            $logicalOpToken = clone $this->_getCurrentToken();
179
            $this->_lexer->nextToken();
180
            $right = $this->_parseLogicalAnd();
181
            FunctionDescription::verifyLogicalOpArguments($logicalOpToken, $left, $right);
182
            $left = new LogicalExpression(
183
                $left, $right, ExpressionType::OR_LOGICAL
184
            );
185
        }
186
187
        $this->_recurseLeave();
188
        return $left;
189
    }
190
191
    /**
192
     * Parse logical and (and).
193
     * 
194
     * @return AbstractExpression
195
     */
196 View Code Duplication
    private function _parseLogicalAnd()
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...
197
    {
198
        $this->_recurseEnter();
199
        $left = $this->_parseComparison();
200
        while ($this->_tokenIdentifierIs(ODataConstants::KEYWORD_AND)) {
201
            $logicalOpToken = clone $this->_getCurrentToken();
202
            $this->_lexer->nextToken();
203
            $right = $this->_parseComparison();
204
            FunctionDescription::verifyLogicalOpArguments($logicalOpToken, $left, $right);
205
            $left = new LogicalExpression($left, $right, ExpressionType::AND_LOGICAL );
206
        }
207
208
        $this->_recurseLeave();
209
        return $left;
210
    }
211
212
    /**
213
     * Parse comparison operation (eq, ne, gt, ge, lt, le)
214
     * 
215
     * @return AbstractExpression
216
     */
217
    private function _parseComparison()
218
    {
219
        $this->_recurseEnter();
220
        $left = $this->_parseAdditive();
221
        while ($this->_getCurrentToken()->isComparisonOperator()) {
222
            $comparisonToken = clone $this->_getCurrentToken();
223
            $this->_lexer->nextToken();
224
            $right = $this->_parseAdditive();
225
            $left = self::_generateComparisonExpression(
226
                $left, $right, 
227
                $comparisonToken, $this->_isPHPExpressionProvider
228
            );
229
        }
230
231
        $this->_recurseLeave();
232
        return $left;
233
    }
234
235
    /**
236
     * Parse additive operation (add, sub).
237
     * 
238
     * @return AbstractExpression
239
     */
240
    private function _parseAdditive()
241
    {
242
        $this->_recurseEnter();
243
        $left = $this->_parseMultiplicative();
244
        while ($this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_ADD) 
245
            || $this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_SUB)) {
246
            $additiveToken = clone $this->_getCurrentToken();
247
            $this->_lexer->nextToken();
248
            $right = $this->_parseMultiplicative();
249
            $opReturnType = FunctionDescription::verifyAndPromoteArithmeticOpArguments($additiveToken, $left, $right);
250 View Code Duplication
            if ($additiveToken->identifierIs(ODataConstants::KEYWORD_ADD)) {
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...
251
                $left = new ArithmeticExpression($left, $right, ExpressionType::ADD, $opReturnType);
0 ignored issues
show
Documentation introduced by
$opReturnType is of type object<POData\Providers\Metadata\Type\IType>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
252
            } else {
253
                $left = new ArithmeticExpression($left, $right, ExpressionType::SUBTRACT, $opReturnType );
0 ignored issues
show
Documentation introduced by
$opReturnType is of type object<POData\Providers\Metadata\Type\IType>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
254
            }
255
        }
256
257
        $this->_recurseLeave();
258
        return $left;
259
    }
260
261
    /** 
262
     * Parse multipicative operators (mul, div, mod)
263
     * 
264
     * @return AbstractExpression
265
     */
266
    private function  _parseMultiplicative()
267
    {
268
        $this->_recurseEnter();
269
        $left = $this->_parseUnary();
270
        while ($this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_MULTIPLY) 
271
            || $this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_DIVIDE) 
272
            || $this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_MODULO)
273
        ) {
274
            $multiplicativeToken = clone $this->_getCurrentToken();
275
            $this->_lexer->nextToken();
276
            $right = $this->_parseUnary();
277
            $opReturnType = FunctionDescription::verifyAndPromoteArithmeticOpArguments(
278
                    $multiplicativeToken, $left, $right
279
                );
280
            if ($multiplicativeToken->identifierIs(ODataConstants::KEYWORD_MULTIPLY)) {
281
                $left = new ArithmeticExpression($left, $right, ExpressionType::MULTIPLY, $opReturnType);
0 ignored issues
show
Documentation introduced by
$opReturnType is of type object<POData\Providers\Metadata\Type\IType>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
282 View Code Duplication
            } else if ($multiplicativeToken->identifierIs(ODataConstants::KEYWORD_DIVIDE)) {
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...
283
                $left = new ArithmeticExpression($left, $right, ExpressionType::DIVIDE, $opReturnType);
0 ignored issues
show
Documentation introduced by
$opReturnType is of type object<POData\Providers\Metadata\Type\IType>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
284
            } else {                
285
                $left = new ArithmeticExpression($left, $right, ExpressionType::MODULO, $opReturnType);
0 ignored issues
show
Documentation introduced by
$opReturnType is of type object<POData\Providers\Metadata\Type\IType>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
286
            }
287
        }
288
289
        $this->_recurseLeave();
290
        return $left;
291
    }
292
293
    /**
294
     * Parse unary operator (- ,not)
295
     * 
296
     * @return AbstractExpression
297
     */
298
    private function _parseUnary()
299
    {
300
        $this->_recurseEnter();
301
       
302
        if ($this->_getCurrentToken()->Id == ExpressionTokenId::MINUS 
303
            || $this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_NOT)
304
        ) {
305
            $op = clone $this->_getCurrentToken();
306
            $this->_lexer->nextToken();            
307
            if ($op->Id == ExpressionTokenId::MINUS 
308
                && (ExpressionLexer::isNumeric($this->_getCurrentToken()->Id))
309
            ) {
310
                $numberLiteral = $this->_getCurrentToken();
311
                $numberLiteral->Text = '-' . $numberLiteral->Text;
312
                $numberLiteral->Position = $op->Position;
313
                $v = $this->_getCurrentToken();
0 ignored issues
show
Unused Code introduced by
$v is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
314
                $this->_setCurrentToken($numberLiteral);
315
                $this->_recurseLeave();
316
                return $this->_parsePrimary();
317
            }
318
319
            $expr = $this->_parsePrimary();
320
            FunctionDescription::validateUnaryOpArguments($op, $expr);
321
            if ($op->Id == ExpressionTokenId::MINUS) {
322
                $expr = new UnaryExpression($expr, ExpressionType::NEGATE, $expr->getType());
0 ignored issues
show
Documentation introduced by
$expr is of type object<POData\UriProcess...ons\AbstractExpression>, but the function expects a object<POData\UriProcess...pressions\unknown_type>.

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...
Documentation introduced by
$expr->getType() is of type object<POData\Providers\Metadata\Type\IType>, but the function expects a object<POData\UriProcess...pressions\unknown_type>.

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...
323
            } else {
324
                $expr = new UnaryExpression($expr, ExpressionType::NOT_LOGICAL, new Boolean());
0 ignored issues
show
Documentation introduced by
$expr is of type object<POData\UriProcess...ons\AbstractExpression>, but the function expects a object<POData\UriProcess...pressions\unknown_type>.

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...
Documentation introduced by
new \POData\Providers\Metadata\Type\Boolean() is of type object<POData\Providers\Metadata\Type\Boolean>, but the function expects a object<POData\UriProcess...pressions\unknown_type>.

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...
325
            }
326
327
            $this->_recurseLeave();
328
            return $expr;
329
        }
330
331
        $this->_recurseLeave();
332
        return $this->_parsePrimary();
333
    }
334
335
    /**
336
     * Start parsing the primary.
337
     *  
338
     * @return AbstractExpression
339
     */
340
    private function _parsePrimary()
341
    {
342
        $this->_recurseEnter();
343
        $expr = $this->_parsePrimaryStart();
344 View Code Duplication
        while (true) {
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...
345
            if ($this->_getCurrentToken()->Id == ExpressionTokenId::SLASH) {
346
                $this->_lexer->nextToken();
347
                $expr = $this->_parsePropertyAccess($expr);
0 ignored issues
show
Compatibility introduced by
$expr of type object<POData\UriProcess...ons\AbstractExpression> is not a sub-type of object<POData\UriProcess...opertyAccessExpression>. It seems like you assume a child class of the class POData\UriProcessor\Quer...ions\AbstractExpression to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
348
            } else {
349
                break;
350
            }
351
        }
352
353
        $this->_recurseLeave();
354
        return $expr;
355
    }
356
357
    /** 
358
     * Parse primary tokens [literals, identifiers (e.g. function call), open param for sub expressions]
359
     *
360
     * 
361
     * @return AbstractExpression
362
     */
363
    private function _parsePrimaryStart()
364
    {
365
        switch ($this->_lexer->getCurrentToken()->Id) {
366
        case ExpressionTokenId::BOOLEAN_LITERAL:
367
            return $this->_parseTypedLiteral(new Boolean());
368
        case ExpressionTokenId::DATETIME_LITERAL:
369
            return $this->_parseTypedLiteral(new DateTime());
370
        case ExpressionTokenId::DECIMAL_LITERAL:
371
            return $this->_parseTypedLiteral(new Decimal());
372
        case ExpressionTokenId::NULL_LITERAL:
373
            return $this->_parseNullLiteral();
374
        case ExpressionTokenId::IDENTIFIER:
375
            return $this->_parseIdentifier();
376
        case ExpressionTokenId::STRING_LITERAL:
377
            return $this->_parseTypedLiteral(new StringType());
378
        case ExpressionTokenId::INT64_LITERAL:
379
            return $this->_parseTypedLiteral(new Int64());
380
        case ExpressionTokenId::INTEGER_LITERAL:
381
            return $this->_parseTypedLiteral(new Int32());
382
        case ExpressionTokenId::DOUBLE_LITERAL:
383
            return $this->_parseTypedLiteral(new Double());
384
        case ExpressionTokenId::SINGLE_LITERAL:
385
            return $this->_parseTypedLiteral(new Single());
386
        case ExpressionTokenId::GUID_LITERAL:
387
            return $this->_parseTypedLiteral(new Guid());
388
        case ExpressionTokenId::BINARY_LITERAL:
389
            throw new NotImplementedException(
390
                'Support for binary is not implemented'
391
            );
392
            //return $this->_parseTypedLiteral(new Binary());
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% 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...
393
        case ExpressionTokenId::OPENPARAM:
394
            return $this->_parseParenExpression();
395
        default:
396
            throw ODataException::createSyntaxError("Expression expected.");
397
        }
398
    }
399
400
    /** 
401
     * Parse Sub expression.
402
     * 
403
     * @return AbstractExpression
404
     */
405 View Code Duplication
    private function _parseParenExpression()
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...
406
    {
407
        if ($this->_getCurrentToken()->Id != ExpressionTokenId::OPENPARAM) {
408
            throw ODataException::createSyntaxError("Open parenthesis expected.");
409
        }
410
411
        $this->_lexer->nextToken();
412
        $expr = $this->_parseExpression();
413
        if ($this->_getCurrentToken()->Id != ExpressionTokenId::CLOSEPARAM) {
414
            throw ODataException::createSyntaxError("Close parenthesis expected.");
415
        }
416
417
        $this->_lexer->nextToken();
418
        return $expr;
419
    }
420
421
    /**
422
     * Parse an identifier
423
     * 
424
     * @return AbstractExpression
425
     */
426
    private function _parseIdentifier()
427
    {
428
        $this->_validateToken(ExpressionTokenId::IDENTIFIER);
429
430
        // An open paren here would indicate calling a method        
431
        $identifierIsFunction = $this->_lexer->peekNextToken()->Id == ExpressionTokenId::OPENPARAM;
432
        if ($identifierIsFunction) {
433
            return $this->_parseIdentifierAsFunction();
434
        } else {
435
            return $this->_parsePropertyAccess(null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<POData\UriProcess...opertyAccessExpression>.

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...
436
        }
437
    }
438
439
    /**
440
     * Parse a property access
441
     * 
442
     * @param PropertyAccessExpression $parentExpression Parent expression.
443
     * 
444
     * @throws ODataException
445
     * 
446
     * @return PropertyAccessExpression
447
     */
448
    private function _parsePropertyAccess($parentExpression)
449
    {
450
        $identifier = $this->_getCurrentToken()->getIdentifier(); 
451
        if (is_null($parentExpression)) {
452
            $parentResourceType = $this->_resourceType;
453
        } else {
454
            $parentResourceType = $parentExpression->getResourceType();
455
            $this->_hasLevel2PropertyInTheExpression = true;
456
        }
457
458
        $resourceProperty = $parentResourceType->resolveProperty($identifier);
459
        if (is_null($resourceProperty)) {
460
            throw ODataException::createSyntaxError(
461
                Messages::expressionLexerNoPropertyInType(
462
                    $identifier, 
463
                    $parentResourceType->getFullName(), 
464
                    $this->_getCurrentToken()->Position
465
                )
466
            );
467
        }
468
469
        if ($resourceProperty->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE) {
470
            throw ODataException::createSyntaxError(
471
                Messages::expressionParserEntityCollectionNotAllowedInFilter(
472
                    $resourceProperty->getName(), 
473
                    $parentResourceType->getFullName(), 
474
                    $this->_getCurrentToken()->Position
475
                )
476
            );
477
        }
478
479
        $exp = new PropertyAccessExpression($parentExpression, $resourceProperty); 
0 ignored issues
show
Bug introduced by
It seems like $parentExpression 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...
480
        $this->_lexer->nextToken();
481
        return $exp;
482
    }
483
484
    /** 
485
     * Try to parse an identifier which is followed by an opern bracket as
486
     * astoria URI function call.
487
     * 
488
     * @return AbstractExpression
489
     * 
490
     * @throws ODataException
491
     */
492
    private function _parseIdentifierAsFunction()
493
    {
494
        $functionToken = clone $this->_getCurrentToken();
495
        $functions = FunctionDescription::verifyFunctionExists($functionToken);
496
        $this->_lexer->nextToken();
497
        $paramExpressions = $this->_parseArgumentList();
498
        $function = FunctionDescription::verifyFunctionCallOpArguments(
499
            $functions, $paramExpressions, $functionToken
500
        );
501
        return new FunctionCallExpression($function, $paramExpressions);
502
    }
503
504
    /**
505
     * Start parsing argument list of a function-call
506
     * 
507
     * @return array<AbstractExpression>
508
     */
509 View Code Duplication
    private function _parseArgumentList()
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...
510
    {
511
        if ($this->_getCurrentToken()->Id != ExpressionTokenId::OPENPARAM) {
512
            throw ODataException::createSyntaxError("Open parenthesis expected.");
513
        }
514
515
        $this->_lexer->nextToken();
516
        $args 
517
            = $this->_getCurrentToken()->Id != ExpressionTokenId::CLOSEPARAM 
518
             ? $this->_parseArguments() : array();
519
        if ($this->_getCurrentToken()->Id != ExpressionTokenId::CLOSEPARAM) {
520
            throw ODataException::createSyntaxError("Close parenthesis expected.");
521
        }
522
523
        $this->_lexer->nextToken();
524
        return $args;
525
    }
526
527
    /** 
528
     * Parse arguments of  a function-call.
529
     * 
530
     * @return array<AbstractExpression>
531
     */
532
    private function _parseArguments()
533
    {
534
        $argList = array();
535 View Code Duplication
        while (true) {
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...
536
            $argList[] = $this->_parseExpression();
537
            if ($this->_getCurrentToken()->Id != ExpressionTokenId::COMMA) {
538
                break;
539
            }
540
541
            $this->_lexer->nextToken();
542
        }
543
544
        return $argList;
545
    }
546
547
    /**
548
     * Parse primitive type literal.
549
     * 
550
     * @param IType $targetType Expected type of the current literal.
551
     * 
552
     * @return AbstractExpression
553
     * 
554
     * @throws ODataException
555
     */
556
    private function _parseTypedLiteral(IType $targetType)
557
    {
558
        $literal = $this->_lexer->getCurrentToken()->Text;
559
        $outVal = null;
560
        if (!$targetType->validate($literal, $outVal)) {
561
            throw ODataException::createSyntaxError(
562
                Messages::expressionParserUnrecognizedLiteral(
563
                    $targetType->getFullTypeName(), 
564
                    $literal, 
565
                    $this->_lexer->getCurrentToken()->Position
566
                )
567
            );
568
        }
569
570
        $result = new ConstantExpression($outVal, $targetType);
0 ignored issues
show
Documentation introduced by
$targetType is of type object<POData\Providers\Metadata\Type\IType>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
571
        $this->_lexer->nextToken();
572
        return $result;
573
    }
574
575
    /**
576
     * Parse null literal.
577
     * 
578
     * @return ConstantExpression
579
     */
580
    private function _parseNullLiteral()
581
    {
582
        $this->_lexer->nextToken();
583
        return new ConstantExpression(null, new Null1());
0 ignored issues
show
Documentation introduced by
new \POData\Providers\Metadata\Type\Null1() is of type object<POData\Providers\Metadata\Type\Null1>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
584
    }
585
586
    /**
587
     * Check the current token is of a specific kind
588
     * 
589
     * @param ExpressionTokenId $expressionTokenId Token to check 
590
     *                                             with current token.
591
     * 
592
     * @return boolean
593
     */
594
    private function _tokenIdentifierIs($expressionTokenId)
595
    {
596
        return $this->_getCurrentToken()->identifierIs($expressionTokenId);
597
    }
598
599
    /**
600
     * Validate the current token
601
     * 
602
     * @param ExpressionTokenId $expressionTokenId Token to check 
603
     *                                             with current token.
604
     * 
605
     * @return void 
606
     * 
607
     * @throws ODataException
608
     */
609
    private function _validateToken($expressionTokenId)
610
    {
611
        if ($this->_getCurrentToken()->Id != $expressionTokenId) {
612
            throw ODataException::createSyntaxError("Syntax error.");
613
        }
614
    }
615
616
    /**
617
     * Increment recursion count and throw error if beyond limit
618
     * 
619
     * @return void 
620
     * 
621
     * @throws ODataException If max recursion limit hits.
622
     */
623
    private function _recurseEnter()
624
    {
625
        $this->_recursionDepth++;
626
        if ($this->_recursionDepth == self::RECURSION_LIMIT) {
627
            throw ODataException::createSyntaxError("Recursion limit reached.");
628
        }
629
    }
630
631
    /**
632
     * Decrement recursion count
633
     * 
634
     * @return void
635
     */
636
    private function _recurseLeave()
637
    {
638
        $this->_recursionDepth--;
639
    }
640
641
    /**
642
     * Generates Comparison Expression
643
     * 
644
     * @param AbstractExpression $left                       The LHS expression.
645
     * @param AbstractExpression $right                      The RHS expression.
646
     * @param ExpressionToken    $expressionToken            The comparison expression token.
647
     * @param boolean            $isPHPExpressionProvider
648
     * 
649
     * @return AbstractExpression
650
     */
651
    private static function _generateComparisonExpression($left, $right, $expressionToken, $isPHPExpressionProvider)
652
    {
653
        FunctionDescription::verifyRelationalOpArguments($expressionToken, $left, $right);
654
655
        //We need special handling for comparison of following types:
656
        //1. String
657
        //2. DateTime
658
        //3. Guid
659
        //4. Binary
660
        //Will make these comparison as function calls, which will 
661
        // be converted to language specific function call by expression 
662
        // provider
663
        $string = new StringType();
664 View Code Duplication
        if ($left->typeIs($string) && $right->typeIs($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...
665
            $strcmpFunctions = FunctionDescription::stringComparisonFunctions();
666
            $left = new FunctionCallExpression($strcmpFunctions[0], array($left, $right));
667
            $right = new ConstantExpression(0, new Int32());  
0 ignored issues
show
Documentation introduced by
new \POData\Providers\Metadata\Type\Int32() is of type object<POData\Providers\Metadata\Type\Int32>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
668
        }
669
670
        $dateTime = new DateTime();
671 View Code Duplication
        if ($left->typeIs($dateTime) && $right->typeIs($dateTime)) {
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...
672
            $dateTimeCmpFunctions = FunctionDescription::dateTimeComparisonFunctions();
673
            $left = new FunctionCallExpression( $dateTimeCmpFunctions[0], array($left, $right));
674
            $right = new ConstantExpression(0, new Int32());
0 ignored issues
show
Documentation introduced by
new \POData\Providers\Metadata\Type\Int32() is of type object<POData\Providers\Metadata\Type\Int32>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
675
        }
676
677
        $guid = new Guid();
678 View Code Duplication
        if ($left->typeIs($guid) && $right->typeIs($guid)) {
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...
679
            $guidEqualityFunctions = FunctionDescription::guidEqualityFunctions();
680
            $left = new FunctionCallExpression($guidEqualityFunctions[0], array($left, $right));
681
            $right = new ConstantExpression(true, new Boolean());
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

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...
Documentation introduced by
new \POData\Providers\Metadata\Type\Boolean() is of type object<POData\Providers\Metadata\Type\Boolean>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
682
        }
683
684
        $binary = new Binary();
685 View Code Duplication
        if ($left->typeIs($binary) && $right->typeIs($binary)) {
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...
686
            $binaryEqualityFunctions = FunctionDescription::binaryEqualityFunctions();
687
            $left = new FunctionCallExpression($binaryEqualityFunctions[0], array($left, $right));
688
            $right = new ConstantExpression(true, new Boolean());
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string.

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...
Documentation introduced by
new \POData\Providers\Metadata\Type\Boolean() is of type object<POData\Providers\Metadata\Type\Boolean>, but the function expects a object<POData\UriProcess...rser\Expressions\IType>.

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...
689
        }
690
691
        $null = new Null1();
692
        if ($left->typeIs($null) || $right->typeIs($null)) {
693
          // If the end user is responsible for implementing IExpressionProvider
694
          // then the sub-tree for a nullability check would be:
695
          //
696
          //          RelationalExpression(EQ/NE)
697
          //                    |
698
          //               ------------
699
          //               |           |
700
          //               |           |
701
          //            CustomerID    NULL
702
          //
703
          // Otherwise (In case of default PHPExpressionProvider):
704
          //          
705
          //  CustomerID eq null
706
          //  ==================
707
          //
708
          //              FunctionCallExpression(is_null)
709
          //                       |
710
          //                       |- Signature => bool (typeof(CustomerID))
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
711
          //                       |- args => {CustomerID}
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% 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...
712
          //
713
          //
714
          //  CustomerID ne null
715
          //  ==================
716
          //
717
          //              UnaryExpression (not)
718
          //                       |
719
          //              FunctionCallExpression(is_null)
720
          //                       |
721
          //                       |- Signature => bool (typeof(CustomerID))
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% 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...
722
          //                       |- args => {CustomerID}
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% 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...
723
          //
724
            if ($isPHPExpressionProvider) {
725
                $arg = $left->typeIs($null) ? $right : $left;
726
                $isNullFunctionDescription = new FunctionDescription('is_null', new Boolean(), array($arg->getType()));
727
                switch ($expressionToken->Text) {
728
	                case ODataConstants::KEYWORD_EQUAL:
729
	                    return new FunctionCallExpression($isNullFunctionDescription, array($arg));
730
	                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
731
732
	                case ODataConstants::KEYWORD_NOT_EQUAL:
733
	                    return new UnaryExpression(
734
	                        new FunctionCallExpression($isNullFunctionDescription, array($arg)),
0 ignored issues
show
Documentation introduced by
new \POData\UriProcessor...scription, array($arg)) is of type object<POData\UriProcess...FunctionCallExpression>, but the function expects a object<POData\UriProcess...pressions\unknown_type>.

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...
735
	                        ExpressionType::NOT_LOGICAL,
736
	                        new Boolean()
0 ignored issues
show
Documentation introduced by
new \POData\Providers\Metadata\Type\Boolean() is of type object<POData\Providers\Metadata\Type\Boolean>, but the function expects a object<POData\UriProcess...pressions\unknown_type>.

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...
737
	                    );
738
	                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
739
                }
740
            }
741
        }
742
743
        switch ($expressionToken->Text) {
744
	        case ODataConstants::KEYWORD_EQUAL:
745
	            return new RelationalExpression(
746
	                $left, $right, ExpressionType::EQUAL
747
	            );
748
	        case ODataConstants::KEYWORD_NOT_EQUAL:
749
	            return new RelationalExpression(
750
	                $left, $right, ExpressionType::NOTEQUAL
751
	            );
752
	        case ODataConstants::KEYWORD_GREATERTHAN:
753
	            return new RelationalExpression(
754
	                $left, $right, ExpressionType::GREATERTHAN
755
	            );
756
	        case ODataConstants::KEYWORD_GREATERTHAN_OR_EQUAL:
757
	            return new RelationalExpression(
758
	                $left, $right, ExpressionType::GREATERTHAN_OR_EQUAL
759
	            );
760
	        case ODataConstants::KEYWORD_LESSTHAN:
761
	            return new RelationalExpression(
762
	                $left, $right, ExpressionType::LESSTHAN
763
	            );
764
	        default:
765
	            return new RelationalExpression(
766
	                $left, $right, ExpressionType::LESSTHAN_OR_EQUAL
767
	            );
768
        }
769
    }
770
771
}