Passed
Push — master ( 4ab488...bcfbc7 )
by Bálint
03:58
created

ExpressionParser::_parseMultiplicative()   A

Complexity

Conditions 6
Paths 4

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
dl 0
loc 25
c 0
b 0
f 0
rs 9.0444
cc 6
nc 4
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\NullType;
19
use POData\Providers\Metadata\Type\IType;
20
use POData\Providers\Metadata\ResourceType;
21
use POData\Providers\Metadata\ResourcePropertyKind;
22
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\AbstractExpression;
23
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\PropertyAccessExpression;
24
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ArithmeticExpression;
25
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\RelationalExpression;
26
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\LogicalExpression;
27
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\FunctionCallExpression;
28
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\UnaryExpression;
29
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ConstantExpression;
30
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ExpressionType;
31
use POData\UriProcessor\QueryProcessor\FunctionDescription;
32
use POData\Common\NotImplementedException;
33
34
35
/**
36
 * Class ExpressionParser
37
 * @package POData\UriProcessor\QueryProcessor\ExpressionParser
38
 */
39
class ExpressionParser
40
{
41
    const RECURSION_LIMIT = 200;
42
43
    /**
44
     * The Lexical analyzer
45
     *
46
     * @var ExpressionLexer
47
     */
48
    private $_lexer;
49
50
    /**
51
     * The current recursion depth
52
     *
53
     * @var int
54
     */
55
    private $_recursionDepth;
56
57
    /**
58
     * The ResourceType on which $filter condition needs to be applied
59
     *
60
     * @var ResourceType
61
     */
62
    private $_resourceType;
63
64
    /**
65
     * @var bool
66
     */
67
    private $_isPHPExpressionProvider;
68
69
    /**
70
     * True if the filter expression contains level 2 property access, for example
71
     * Customers?$filter=Address/LineNumber eq 12
72
     * Customer?$filter=Order/OrderID gt 1234
73
     * False otherwise.
74
     *
75
     * @var bool
76
     */
77
    private $_hasLevel2PropertyInTheExpression;
78
79
80
    /**
81
     * Construct a new instance of ExpressionParser
82
     *
83
     * @param string $text The expression to parse.
84
     * @param ResourceType $resourceType The resource type of the resource targeted by the resource path.
85
     * @param bool $isPHPExpressionProvider
86
     *
87
     * TODO Expression parser should not depend on the fact that end user is implementing IExpressionProvider or not.
88
     */
89
    public function __construct($text, ResourceType $resourceType, $isPHPExpressionProvider)
90
    {
91
        $this->_lexer = new ExpressionLexer($text);
92
        $this->_resourceType = $resourceType;
93
        $this->_isPHPExpressionProvider = $isPHPExpressionProvider;
94
        $this->_hasLevel2PropertyInTheExpression = false;
95
    }
96
97
    /**
98
     * Checks whether the expression contains level 2 property access.
99
     *
100
     * @return boolean
101
     */
102
    public function hasLevel2Property()
103
    {
104
        return $this->_hasLevel2PropertyInTheExpression;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_hasLevel2PropertyInTheExpression returns the type boolean which is incompatible with the documented return type POData\Providers\Metadata\Type\Boolean.
Loading history...
105
    }
106
107
    /**
108
     * Get the current token from lexer
109
     *
110
     * @return ExpressionToken
111
     */
112
    private function _getCurrentToken()
113
    {
114
        return $this->_lexer->getCurrentToken();
115
    }
116
117
    /**
118
     * Set the current token in lexer
119
     *
120
     * @param ExpressionToken $token The token to set as current token.
121
     *
122
     * @return void
123
     */
124
    private function _setCurrentToken($token)
125
    {
126
        $this->_lexer->setCurrentToken($token);
127
    }
128
129
    /**
130
     * Resets parser with new expression string.
131
     *
132
     * @param string $text Reset the expression to parse.
133
     *
134
     * @return void
135
     */
136
    public function resetParser($text)
137
    {
138
        $this->_lexer = new ExpressionLexer($text);
139
        $this->_recursionDepth = 0;
140
    }
141
142
    /**
143
     * Parse the expression in filter option.
144
     *
145
     * @return AbstractExpression
146
     */
147
    public function parseFilter()
148
    {
149
        return $this->_parseExpression();
150
    }
151
152
    /**
153
     * Start parsing the expression
154
     *
155
     * @return AbstractExpression
156
     */
157
    private function _parseExpression()
158
    {
159
        $this->_recurseEnter();
160
        $expr = $this->_parseLogicalOr();
161
        $this->_recurseLeave();
162
        return $expr;
163
    }
164
165
    /**
166
     * Parse logical or (or)
167
     *
168
     * @return AbstractExpression
169
     */
170
    private function _parseLogicalOr()
171
    {
172
        $this->_recurseEnter();
173
        $left = $this->_parseLogicalAnd();
174
        while ($this->_tokenIdentifierIs(ODataConstants::KEYWORD_OR)) {
0 ignored issues
show
Bug introduced by
POData\Common\ODataConstants::KEYWORD_OR of type string is incompatible with the type POData\UriProcessor\Quer...arser\ExpressionTokenId expected by parameter $expressionTokenId of POData\UriProcessor\Quer...r::_tokenIdentifierIs(). ( Ignorable by Annotation )

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

174
        while ($this->_tokenIdentifierIs(/** @scrutinizer ignore-type */ ODataConstants::KEYWORD_OR)) {
Loading history...
175
            $logicalOpToken = clone $this->_getCurrentToken();
176
            $this->_lexer->nextToken();
177
            $right = $this->_parseLogicalAnd();
178
            FunctionDescription::verifyLogicalOpArguments($logicalOpToken, $left, $right);
179
            $left = new LogicalExpression(
180
                $left, $right, ExpressionType::OR_LOGICAL
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...ressionType::OR_LOGICAL of type integer is incompatible with the type POData\UriProcessor\Quer...ressions\ExpressionType expected by parameter $nodeType of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

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

180
                $left, $right, /** @scrutinizer ignore-type */ ExpressionType::OR_LOGICAL
Loading history...
181
            );
182
        }
183
184
        $this->_recurseLeave();
185
        return $left;
186
    }
187
188
    /**
189
     * Parse logical and (and).
190
     *
191
     * @return AbstractExpression
192
     */
193
    private function _parseLogicalAnd()
194
    {
195
        $this->_recurseEnter();
196
        $left = $this->_parseComparison();
197
        while ($this->_tokenIdentifierIs(ODataConstants::KEYWORD_AND)) {
0 ignored issues
show
Bug introduced by
POData\Common\ODataConstants::KEYWORD_AND of type string is incompatible with the type POData\UriProcessor\Quer...arser\ExpressionTokenId expected by parameter $expressionTokenId of POData\UriProcessor\Quer...r::_tokenIdentifierIs(). ( Ignorable by Annotation )

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

197
        while ($this->_tokenIdentifierIs(/** @scrutinizer ignore-type */ ODataConstants::KEYWORD_AND)) {
Loading history...
198
            $logicalOpToken = clone $this->_getCurrentToken();
199
            $this->_lexer->nextToken();
200
            $right = $this->_parseComparison();
201
            FunctionDescription::verifyLogicalOpArguments($logicalOpToken, $left, $right);
202
            $left = new LogicalExpression($left, $right, ExpressionType::AND_LOGICAL);
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...essionType::AND_LOGICAL of type integer is incompatible with the type POData\UriProcessor\Quer...ressions\ExpressionType expected by parameter $nodeType of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

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

202
            $left = new LogicalExpression($left, $right, /** @scrutinizer ignore-type */ ExpressionType::AND_LOGICAL);
Loading history...
203
        }
204
205
        $this->_recurseLeave();
206
        return $left;
207
    }
208
209
    /**
210
     * Parse comparison operation (eq, ne, gt, ge, lt, le)
211
     * TODO: http://host/service/Products?$filter=Name in ('Milk', 'Cheese')
212
     * TODO: http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html
213
     * @return AbstractExpression
214
     */
215
    private function _parseComparison()
216
    {
217
        $this->_recurseEnter();
218
        $left = $this->_parseAdditive();
219
        while ($this->_getCurrentToken()->isComparisonOperator()) {
220
            $comparisonToken = clone $this->_getCurrentToken();
221
            $this->_lexer->nextToken();
222
            $right = $this->_parseAdditive();
223
            $left = self::_generateComparisonExpression(
224
                $left, $right,
225
                $comparisonToken, $this->_isPHPExpressionProvider
0 ignored issues
show
Bug introduced by
$this->_isPHPExpressionProvider of type boolean is incompatible with the type POData\Providers\Metadata\Type\Boolean expected by parameter $isPHPExpressionProvider of POData\UriProcessor\Quer...eComparisonExpression(). ( Ignorable by Annotation )

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

225
                $comparisonToken, /** @scrutinizer ignore-type */ $this->_isPHPExpressionProvider
Loading history...
226
            );
227
        }
228
229
        $this->_recurseLeave();
230
        return $left;
231
    }
232
233
    /**
234
     * Parse additive operation (add, sub).
235
     *
236
     * @return AbstractExpression
237
     */
238
    private function _parseAdditive()
239
    {
240
        $this->_recurseEnter();
241
        $left = $this->_parseMultiplicative();
242
        while ($this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_ADD)
0 ignored issues
show
Bug introduced by
POData\Common\ODataConstants::KEYWORD_ADD of type string is incompatible with the type POData\UriProcessor\Quer...arser\ExpressionTokenId expected by parameter $id of POData\UriProcessor\Quer...onToken::identifierIs(). ( Ignorable by Annotation )

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

242
        while ($this->_getCurrentToken()->identifierIs(/** @scrutinizer ignore-type */ ODataConstants::KEYWORD_ADD)
Loading history...
243
            || $this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_SUB)) {
244
            $additiveToken = clone $this->_getCurrentToken();
245
            $this->_lexer->nextToken();
246
            $right = $this->_parseMultiplicative();
247
            $opReturnType = FunctionDescription::verifyAndPromoteArithmeticOpArguments($additiveToken, $left, $right);
248
            if ($additiveToken->identifierIs(ODataConstants::KEYWORD_ADD)) {
249
                $left = new ArithmeticExpression($left, $right, ExpressionType::ADD, $opReturnType);
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...ons\ExpressionType::ADD of type integer is incompatible with the type POData\UriProcessor\Quer...ressions\ExpressionType expected by parameter $nodeType of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

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

249
                $left = new ArithmeticExpression($left, $right, /** @scrutinizer ignore-type */ ExpressionType::ADD, $opReturnType);
Loading history...
250
            } else {
251
                $left = new ArithmeticExpression($left, $right, ExpressionType::SUBTRACT, $opReturnType);
252
            }
253
        }
254
255
        $this->_recurseLeave();
256
        return $left;
257
    }
258
259
    /**
260
     * Parse multipicative operators (mul, div, mod)
261
     *
262
     * @return AbstractExpression
263
     */
264
    private function  _parseMultiplicative()
265
    {
266
        $this->_recurseEnter();
267
        $left = $this->_parseUnary();
268
        while ($this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_MULTIPLY)
0 ignored issues
show
Bug introduced by
POData\Common\ODataConstants::KEYWORD_MULTIPLY of type string is incompatible with the type POData\UriProcessor\Quer...arser\ExpressionTokenId expected by parameter $id of POData\UriProcessor\Quer...onToken::identifierIs(). ( Ignorable by Annotation )

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

268
        while ($this->_getCurrentToken()->identifierIs(/** @scrutinizer ignore-type */ ODataConstants::KEYWORD_MULTIPLY)
Loading history...
269
            || $this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_DIVIDE)
270
            || $this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_MODULO)
271
        ) {
272
            $multiplicativeToken = clone $this->_getCurrentToken();
273
            $this->_lexer->nextToken();
274
            $right = $this->_parseUnary();
275
            $opReturnType = FunctionDescription::verifyAndPromoteArithmeticOpArguments(
276
                    $multiplicativeToken, $left, $right
277
                );
278
            if ($multiplicativeToken->identifierIs(ODataConstants::KEYWORD_MULTIPLY)) {
279
                $left = new ArithmeticExpression($left, $right, ExpressionType::MULTIPLY, $opReturnType);
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...xpressionType::MULTIPLY of type integer is incompatible with the type POData\UriProcessor\Quer...ressions\ExpressionType expected by parameter $nodeType of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

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

279
                $left = new ArithmeticExpression($left, $right, /** @scrutinizer ignore-type */ ExpressionType::MULTIPLY, $opReturnType);
Loading history...
280
            } else if ($multiplicativeToken->identifierIs(ODataConstants::KEYWORD_DIVIDE)) {
281
                $left = new ArithmeticExpression($left, $right, ExpressionType::DIVIDE, $opReturnType);
282
            } else {
283
                $left = new ArithmeticExpression($left, $right, ExpressionType::MODULO, $opReturnType);
284
            }
285
        }
286
287
        $this->_recurseLeave();
288
        return $left;
289
    }
290
291
    /**
292
     * Parse unary operator (- ,not)
293
     *
294
     * @return AbstractExpression
295
     */
296
    private function _parseUnary()
297
    {
298
        $this->_recurseEnter();
299
300
        if ($this->_getCurrentToken()->Id == ExpressionTokenId::MINUS
301
            || $this->_getCurrentToken()->identifierIs(ODataConstants::KEYWORD_NOT)
0 ignored issues
show
Bug introduced by
POData\Common\ODataConstants::KEYWORD_NOT of type string is incompatible with the type POData\UriProcessor\Quer...arser\ExpressionTokenId expected by parameter $id of POData\UriProcessor\Quer...onToken::identifierIs(). ( Ignorable by Annotation )

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

301
            || $this->_getCurrentToken()->identifierIs(/** @scrutinizer ignore-type */ ODataConstants::KEYWORD_NOT)
Loading history...
302
        ) {
303
            $op = clone $this->_getCurrentToken();
304
            $this->_lexer->nextToken();
305
            if ($op->Id == ExpressionTokenId::MINUS
0 ignored issues
show
introduced by
The condition $op->Id is always false. If $op->Id can have other possible types, add them to src/POData/UriProcessor/...ser/ExpressionToken.php:16
Loading history...
306
                && (ExpressionLexer::isNumeric($this->_getCurrentToken()->Id))
0 ignored issues
show
Bug introduced by
$this->_getCurrentToken()->Id of type void is incompatible with the type POData\UriProcessor\Quer...arser\ExpressionTokenId expected by parameter $id of POData\UriProcessor\Quer...ssionLexer::isNumeric(). ( Ignorable by Annotation )

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

306
                && (ExpressionLexer::isNumeric(/** @scrutinizer ignore-type */ $this->_getCurrentToken()->Id))
Loading history...
307
            ) {
308
                $numberLiteral = $this->_getCurrentToken();
309
                $numberLiteral->Text = '-' . $numberLiteral->Text;
310
                $numberLiteral->Position = $op->Position;
311
                $v = $this->_getCurrentToken();
0 ignored issues
show
Unused Code introduced by
The assignment to $v is dead and can be removed.
Loading history...
312
                $this->_setCurrentToken($numberLiteral);
313
                $this->_recurseLeave();
314
                return $this->_parsePrimary();
315
            }
316
317
            $expr = $this->_parsePrimary();
318
            FunctionDescription::validateUnaryOpArguments($op, $expr);
319
            if ($op->Id == ExpressionTokenId::MINUS) {
0 ignored issues
show
introduced by
The condition $op->Id is always false. If $op->Id can have other possible types, add them to src/POData/UriProcessor/...ser/ExpressionToken.php:16
Loading history...
320
                $expr = new UnaryExpression($expr, ExpressionType::NEGATE, $expr->getType());
321
            } else {
322
                $expr = new UnaryExpression($expr, ExpressionType::NOT_LOGICAL, new Boolean());
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...essionType::NOT_LOGICAL of type integer is incompatible with the type POData\UriProcessor\Quer...xpressions\unknown_type expected by parameter $nodeType of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

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

322
                $expr = new UnaryExpression($expr, /** @scrutinizer ignore-type */ ExpressionType::NOT_LOGICAL, new Boolean());
Loading history...
323
            }
324
325
            $this->_recurseLeave();
326
            return $expr;
327
        }
328
329
        $this->_recurseLeave();
330
        return $this->_parsePrimary();
331
    }
332
333
    /**
334
     * Start parsing the primary.
335
     *
336
     * @return AbstractExpression
337
     */
338
    private function _parsePrimary()
339
    {
340
        $this->_recurseEnter();
341
        $expr = $this->_parsePrimaryStart();
342
        while (true) {
343
            if ($this->_getCurrentToken()->Id == ExpressionTokenId::SLASH) {
344
                $this->_lexer->nextToken();
345
                $expr = $this->_parsePropertyAccess($expr);
346
            } else {
347
                break;
348
            }
349
        }
350
351
        $this->_recurseLeave();
352
        return $expr;
353
    }
354
355
    /**
356
     * Parse primary tokens [literals, identifiers (e.g. function call), open param for sub expressions]
357
     *
358
     *
359
     * @return AbstractExpression
360
     */
361
    private function _parsePrimaryStart()
362
    {
363
        switch ($this->_lexer->getCurrentToken()->Id) {
364
            case ExpressionTokenId::BOOLEAN_LITERAL:
365
                return $this->_parseTypedLiteral(new Boolean());
366
            case ExpressionTokenId::DATETIME_LITERAL:
367
                return $this->_parseTypedLiteral(new DateTime());
368
            case ExpressionTokenId::DECIMAL_LITERAL:
369
                return $this->_parseTypedLiteral(new Decimal());
370
            case ExpressionTokenId::NULL_LITERAL:
371
                return $this->_parseNullLiteral();
372
            case ExpressionTokenId::IDENTIFIER:
373
                return $this->_parseIdentifier();
374
            case ExpressionTokenId::STRING_LITERAL:
375
                return $this->_parseTypedLiteral(new StringType());
376
            case ExpressionTokenId::INT64_LITERAL:
377
                return $this->_parseTypedLiteral(new Int64());
378
            case ExpressionTokenId::INTEGER_LITERAL:
379
                return $this->_parseTypedLiteral(new Int32());
380
            case ExpressionTokenId::DOUBLE_LITERAL:
381
                return $this->_parseTypedLiteral(new Double());
382
            case ExpressionTokenId::SINGLE_LITERAL:
383
                return $this->_parseTypedLiteral(new Single());
384
            case ExpressionTokenId::GUID_LITERAL:
385
                return $this->_parseTypedLiteral(new Guid());
386
            case ExpressionTokenId::BINARY_LITERAL:
387
                throw new NotImplementedException(
388
                    'Support for binary is not implemented'
389
                );
390
                //return $this->_parseTypedLiteral(new Binary());
391
            case ExpressionTokenId::OPENPARAM:
392
                return $this->_parseParenExpression();
393
            default:
394
                throw ODataException::createSyntaxError("Expression expected.");
395
        }
396
    }
397
398
    /**
399
     * Parse Sub expression.
400
     *
401
     * @return AbstractExpression
402
     */
403
    private function _parseParenExpression()
404
    {
405
        if ($this->_getCurrentToken()->Id != ExpressionTokenId::OPENPARAM) {
0 ignored issues
show
introduced by
The condition $this->_getCurrentToken()->Id is always true. If $this->_getCurrentToken()->Id can have other possible types, add them to src/POData/UriProcessor/...ser/ExpressionToken.php:16
Loading history...
406
            throw ODataException::createSyntaxError("Open parenthesis expected.");
407
        }
408
409
        $this->_lexer->nextToken();
410
        $expr = $this->_parseExpression();
411
        if ($this->_getCurrentToken()->Id != ExpressionTokenId::CLOSEPARAM) {
412
            throw ODataException::createSyntaxError("Close parenthesis expected.");
413
        }
414
415
        $this->_lexer->nextToken();
416
        return $expr;
417
    }
418
419
    /**
420
     * Parse an identifier
421
     *
422
     * @return AbstractExpression
423
     */
424
    private function _parseIdentifier()
425
    {
426
        $this->_validateToken(ExpressionTokenId::IDENTIFIER);
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...sionTokenId::IDENTIFIER of type integer is incompatible with the type POData\UriProcessor\Quer...arser\ExpressionTokenId expected by parameter $expressionTokenId of POData\UriProcessor\Quer...arser::_validateToken(). ( Ignorable by Annotation )

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

426
        $this->_validateToken(/** @scrutinizer ignore-type */ ExpressionTokenId::IDENTIFIER);
Loading history...
427
428
        // An open paren here would indicate calling a method
429
        $identifierIsFunction = $this->_lexer->peekNextToken()->Id == ExpressionTokenId::OPENPARAM;
430
        if ($identifierIsFunction) {
0 ignored issues
show
introduced by
The condition $identifierIsFunction is always false.
Loading history...
431
            return $this->_parseIdentifierAsFunction();
432
        } else {
433
            return $this->_parsePropertyAccess(null);
434
        }
435
    }
436
437
    /**
438
     * Parse a property access
439
     *
440
     * @param PropertyAccessExpression $parentExpression Parent expression.
441
     *
442
     * @throws ODataException
443
     *
444
     * @return PropertyAccessExpression
445
     */
446
    private function _parsePropertyAccess($parentExpression)
447
    {
448
        $identifier = $this->_getCurrentToken()->getIdentifier();
449
        if (is_null($parentExpression)) {
450
            $parentResourceType = $this->_resourceType;
451
        } else {
452
            $parentResourceType = $parentExpression->getResourceType();
453
            $this->_hasLevel2PropertyInTheExpression = true;
454
        }
455
456
        $resourceProperty = $parentResourceType->resolveProperty($identifier);
457
        if (is_null($resourceProperty)) {
458
            throw ODataException::createSyntaxError(
459
                Messages::expressionLexerNoPropertyInType(
460
                    $identifier,
461
                    $parentResourceType->getFullName(),
462
                    $this->_getCurrentToken()->Position
463
                )
464
            );
465
        }
466
467
        /*
468
        if ($resourceProperty->getKind() == ResourcePropertyKind::RESOURCESET_REFERENCE) {
469
            throw ODataException::createSyntaxError(
470
                Messages::expressionParserEntityCollectionNotAllowedInFilter(
471
                    $resourceProperty->getName(),
472
                    $parentResourceType->getFullName(),
473
                    $this->_getCurrentToken()->Position
474
                )
475
            );
476
        }
477
        */
478
479
        $exp = new PropertyAccessExpression($parentExpression, $resourceProperty);
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
    private function _parseArgumentList()
510
    {
511
        if ($this->_getCurrentToken()->Id != ExpressionTokenId::OPENPARAM) {
0 ignored issues
show
introduced by
The condition $this->_getCurrentToken()->Id is always true. If $this->_getCurrentToken()->Id can have other possible types, add them to src/POData/UriProcessor/...ser/ExpressionToken.php:16
Loading history...
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
        while (true) {
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);
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 NullType());
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);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_getCurren...rIs($expressionTokenId) returns the type boolean which is incompatible with the documented return type POData\Providers\Metadata\Type\Boolean.
Loading history...
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. EdmString
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
        if ($left->typeIs($string) && $right->typeIs($string) && !in_array($expressionToken->Text, [ODataConstants::KEYWORD_EQUAL, ODataConstants::KEYWORD_NOT_EQUAL])) {
665
            $strcmpFunctions = FunctionDescription::stringComparisonFunctions();
666
            $left = new FunctionCallExpression($strcmpFunctions[0], array($left, $right));
667
            $right = new ConstantExpression(0, new Int32());
668
        }
669
670
        $dateTime = new DateTime();
671
        if ($left->typeIs($dateTime) && $right->typeIs($dateTime)) {
672
            $dateTimeCmpFunctions = FunctionDescription::dateTimeComparisonFunctions();
673
            $left = new FunctionCallExpression($dateTimeCmpFunctions[0], array($left, $right));
674
            $right = new ConstantExpression(0, new Int32());
675
        }
676
677
        $guid = new Guid();
678
        if ($left->typeIs($guid) && $right->typeIs($guid)) {
679
            $guidEqualityFunctions = FunctionDescription::guidEqualityFunctions();
680
            $left = new FunctionCallExpression($guidEqualityFunctions[0], array($left, $right));
681
            $right = new ConstantExpression(true, new Boolean());
0 ignored issues
show
Bug introduced by
true of type true is incompatible with the type string expected by parameter $value of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

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

681
            $right = new ConstantExpression(/** @scrutinizer ignore-type */ true, new Boolean());
Loading history...
682
        }
683
684
        $binary = new Binary();
685
        if ($left->typeIs($binary) && $right->typeIs($binary)) {
686
            $binaryEqualityFunctions = FunctionDescription::binaryEqualityFunctions();
687
            $left = new FunctionCallExpression($binaryEqualityFunctions[0], array($left, $right));
688
            $right = new ConstantExpression(true, new Boolean());
689
        }
690
691
        $null = new NullType();
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))
711
            //                       |- args => {CustomerID}
712
            //
713
            //
714
            //  CustomerID ne null
715
            //  ==================
716
            //
717
            //              UnaryExpression (not)
718
            //                       |
719
            //              FunctionCallExpression(is_null)
720
            //                       |
721
            //                       |- Signature => bool (typeof(CustomerID))
722
            //                       |- args => {CustomerID}
723
            //
724
            if ($isPHPExpressionProvider) {
0 ignored issues
show
introduced by
$isPHPExpressionProvider is of type POData\Providers\Metadata\Type\Boolean, thus it always evaluated to true.
Loading history...
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)),
735
                            ExpressionType::NOT_LOGICAL,
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...essionType::NOT_LOGICAL of type integer is incompatible with the type POData\UriProcessor\Quer...xpressions\unknown_type expected by parameter $nodeType of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

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

735
                            /** @scrutinizer ignore-type */ ExpressionType::NOT_LOGICAL,
Loading history...
736
                            new Boolean()
737
                        );
738
                        break;
739
                }
740
            }
741
        }
742
743
        switch ($expressionToken->Text) {
744
            case ODataConstants::KEYWORD_EQUAL:
745
                return new RelationalExpression(
746
                    $left, $right, ExpressionType::EQUAL
0 ignored issues
show
Bug introduced by
POData\UriProcessor\Quer...s\ExpressionType::EQUAL of type integer is incompatible with the type POData\UriProcessor\Quer...ressions\ExpressionType expected by parameter $nodeType of POData\UriProcessor\Quer...pression::__construct(). ( Ignorable by Annotation )

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

746
                    $left, $right, /** @scrutinizer ignore-type */ ExpressionType::EQUAL
Loading history...
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
}
772