Passed
Push — master ( 492d83...a726fe )
by Alex
01:10
created

ExpressionParser2::processFunctionCallNode()   C

Complexity

Conditions 8
Paths 12

Size

Total Lines 41
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 41
rs 5.3846
cc 8
eloc 29
nc 12
nop 2
1
<?php
2
3
namespace POData\UriProcessor\QueryProcessor\ExpressionParser;
4
5
use POData\Common\Messages;
6
use POData\Common\ODataException;
7
use POData\Providers\Expression\IExpressionProvider;
8
use POData\Providers\Expression\PHPExpressionProvider;
9
use POData\Providers\Metadata\ResourceType;
10
use POData\Providers\Metadata\Type\Boolean;
11
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\AbstractExpression;
12
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ArithmeticExpression;
13
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ConstantExpression;
14
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ExpressionType;
15
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\FunctionCallExpression;
16
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\LogicalExpression;
17
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\PropertyAccessExpression;
18
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\RelationalExpression;
19
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\UnaryExpression;
20
use POData\UriProcessor\QueryProcessor\FunctionDescription;
21
22
/**
23
 * Class ExpressionParser2.
24
 *
25
 * Build the basic expression tree for a given expression using base class
26
 * ExpressionParser, modify the expression tree to have null checks
27
 */
28
class ExpressionParser2 extends ExpressionParser
29
{
30
    /**
31
     * @var array
32
     */
33
    private $mapTable;
34
35
    /**
36
     * Collection of navigation properties used in the expression.
37
     *
38
     * @var array(array(ResourceProperty))
39
     */
40
    private $navigationPropertiesUsedInTheExpression;
41
42
    /**
43
     * Indicates whether the end user has implemented IExpressionProvider or not.
44
     *
45
     * @var bool
46
     */
47
    private $isPHPExpressionProvider;
48
49
    /**
50
     * Create new instance of ExpressionParser2.
51
     *
52
     * @param string       $text                    The text expression to parse
53
     * @param ResourceType $resourceType            The resource type in which
54
     *                                              expression will be applied
55
     * @param bool         $isPHPExpressionProvider True if IExpressionProvider provider is
56
     *                                              implemented by user, False otherwise
57
     */
58
    public function __construct($text, ResourceType $resourceType, $isPHPExpressionProvider)
59
    {
60
        parent::__construct($text, $resourceType, $isPHPExpressionProvider);
61
        $this->navigationPropertiesUsedInTheExpression = [];
62
        $this->isPHPExpressionProvider = $isPHPExpressionProvider;
63
    }
64
65
    /**
66
     * Parse and generate expression from the the given odata expression.
67
     *
68
     *
69
     * @param string              $text               The text expression to parse
70
     * @param ResourceType        $resourceType       The resource type in which
71
     * @param IExpressionProvider $expressionProvider Implementation of IExpressionProvider
72
     *
73
     * @throws ODataException If any error occurs while parsing the odata expression or
74
     * building the php/custom expression
75
     *
76
     * @return FilterInfo
77
     */
78
    public static function parseExpression2($text, ResourceType $resourceType, IExpressionProvider $expressionProvider)
79
    {
80
        $expressionParser2 = new self($text, $resourceType, $expressionProvider instanceof PHPExpressionProvider);
81
        $expressionTree = $expressionParser2->parseFilter();
82
83
        $expressionAsString = null;
84
85
        $expressionProvider->setResourceType($resourceType);
86
        $expressionProcessor = new ExpressionProcessor($expressionProvider);
87
88
        try {
89
            $expressionAsString = $expressionProcessor->processExpression($expressionTree);
90
        } catch (\InvalidArgumentException $invalidArgumentException) {
91
            throw ODataException::createInternalServerError($invalidArgumentException->getMessage());
92
        }
93
        $expressionAsString = (null !== $expressionAsString) ? $expressionAsString : "";
94
        return new FilterInfo(
95
            $expressionParser2->navigationPropertiesUsedInTheExpression,
96
            $expressionAsString
97
        );
98
    }
99
100
    /**
101
     * Parse the expression.
102
     *
103
     * @see library/POData/QueryProcessor/ExpressionParser::parseFilter()
104
     *
105
     * @throws ODataException
106
     *
107
     * @return AbstractExpression
108
     */
109
    public function parseFilter()
110
    {
111
        $expression = parent::parseFilter();
112
        if (!$expression->typeIs(new Boolean())) {
113
            throw ODataException::createSyntaxError(
114
                Messages::expressionParser2BooleanRequired()
115
            );
116
        }
117
        if ($this->isPHPExpressionProvider) {
118
            $resultExpression = $this->processNodeForNullability($expression, null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<POData\UriProcess...ons\AbstractExpression>.

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...
119
            if (null != $resultExpression) {
120
                return $resultExpression;
121
            }
122
        }
123
124
        return $expression;
125
    }
126
127
    /**
128
     * Process the expression node for nullability.
129
     *
130
     * @param AbstractExpression $expression            The expression node to process
131
     * @param AbstractExpression $parentExpression      The parent expression of expression node to process
132
     * @param bool               $checkNullForMostChild whether to include null check for current property
133
     *
134
     * @throws ODataException
135
     *
136
     * @return AbstractExpression New expression tree with nullability check
137
     */
138
    private function processNodeForNullability(
139
        $expression,
140
        $parentExpression,
141
        $checkNullForMostChild = true
142
    ) {
143
        if ($expression instanceof ArithmeticExpression) {
144
            return $this->processArithmeticNode($expression);
145
        } elseif ($expression instanceof ConstantExpression) {
146
            return;
147
        } elseif ($expression instanceof FunctionCallExpression) {
148
            return $this->processFunctionCallNode($expression, $parentExpression);
149
        } elseif ($expression instanceof LogicalExpression) {
150
            return $this->processLogicalNode($expression, $parentExpression);
151
        } elseif ($expression instanceof PropertyAccessExpression) {
152
            return $this->processPropertyAccessNode(
153
                $expression,
154
                $parentExpression,
155
                $checkNullForMostChild
156
            );
157
        } elseif ($expression instanceof RelationalExpression) {
158
            return $this->processRelationalNode($expression, $parentExpression);
159
        } elseif ($expression instanceof UnaryExpression) {
160
            return $this->processUnaryNode($expression, $parentExpression);
161
        }
162
163
        throw ODataException::createSyntaxError(
164
            Messages::expressionParser2UnexpectedExpression(get_class($expression))
165
        );
166
    }
167
168
    /**
169
     * Process an arithmetic expression node for nullability.
170
     *
171
     * @param ArithmeticExpression $expression The arithmetic expression node
172
     *                                         to process
173
     *
174
     * @return AbstractExpression|null
175
     */
176
    private function processArithmeticNode(ArithmeticExpression $expression)
177
    {
178
        $leftNullableExpTree = $this->processNodeForNullability(
179
            $expression->getLeft(),
180
            $expression
181
        );
182
        $rightNullableExpTree = $this->processNodeForNullability(
183
            $expression->getRight(),
184
            $expression
185
        );
186
        $resultExpression = null;
0 ignored issues
show
Unused Code introduced by
$resultExpression 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...
187 View Code Duplication
        if ($leftNullableExpTree != null && $rightNullableExpTree != null) {
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...
188
            $resultExpression = $this->mergeNullableExpressionTrees(
189
                $leftNullableExpTree,
190
                $rightNullableExpTree
191
            );
192
        } else {
193
            $resultExpression = $leftNullableExpTree != null
194
                               ? $leftNullableExpTree : $rightNullableExpTree;
195
        }
196
197
        return $resultExpression;
198
    }
199
200
    /**
201
     * Process an arithmetic expression node for nullability.
202
     *
203
     * @param FunctionCallExpression $expression       The function call expression
204
     *                                                 node to process
205
     * @param AbstractExpression     $parentExpression The parent expression of
206
     *                                                 expression node to process
207
     *
208
     * @return null|AbstractExpression
209
     */
210
    private function processFunctionCallNode(
211
        FunctionCallExpression $expression,
212
        $parentExpression
213
    ) {
214
        $paramExpressions = $expression->getParamExpressions();
215
        $checkNullForMostChild
216
            = strcmp(
217
                $expression->getFunctionDescription()->name,
218
                'is_null'
219
            ) === 0;
220
        $resultExpression = null;
221
        foreach ($paramExpressions as $paramExpression) {
222
            $resultExpression1 = $this->processNodeForNullability(
223
                $paramExpression,
224
                $expression,
225
                !$checkNullForMostChild
226
            );
227
            if (null != $resultExpression1 && null != $resultExpression) {
228
                $resultExpression = $this->mergeNullableExpressionTrees(
229
                    $resultExpression,
230
                    $resultExpression1
231
                );
232
            } elseif (null != $resultExpression1 && null == $resultExpression) {
233
                $resultExpression = $resultExpression1;
234
            }
235
        }
236
237
        if (null == $resultExpression) {
238
            return null;
239
        }
240
241
        if (null == $parentExpression) {
242
            return new LogicalExpression(
243
                $resultExpression,
244
                $expression,
245
                ExpressionType::AND_LOGICAL
246
            );
247
        }
248
249
        return $resultExpression;
250
    }
251
252
    /**
253
     * Process an logical expression node for nullability.
254
     *
255
     * @param LogicalExpression  $expression       The logical expression node
256
     *                                             to process
257
     * @param AbstractExpression $parentExpression The parent expression of
258
     *                                             expression node to process
259
     *
260
     * @return null|AbstractExpression
261
     */
262
    private function processLogicalNode(
263
        LogicalExpression $expression,
264
        $parentExpression
265
    ) {
266
        $leftNullableExpTree = $this->processNodeForNullability(
267
            $expression->getLeft(),
268
            $expression
269
        );
270
        $rightNullableExpTree = $this->processNodeForNullability(
271
            $expression->getRight(),
272
            $expression
273
        );
274
        if ($expression->getNodeType() == ExpressionType::OR_LOGICAL) {
275
            if (null !== $leftNullableExpTree) {
276
                $resultExpression = new LogicalExpression(
277
                    $leftNullableExpTree,
278
                    $expression->getLeft(),
279
                    ExpressionType::AND_LOGICAL
280
                );
281
                $expression->setLeft($resultExpression);
282
            }
283
284
            if (null !== $rightNullableExpTree) {
285
                $resultExpression = new LogicalExpression(
286
                    $rightNullableExpTree,
287
                    $expression->getRight(),
288
                    ExpressionType::AND_LOGICAL
289
                );
290
                $expression->setRight($resultExpression);
291
            }
292
293
            return null;
294
        }
295
296
        $resultExpression = null;
0 ignored issues
show
Unused Code introduced by
$resultExpression 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...
297 View Code Duplication
        if (null != $leftNullableExpTree && null != $rightNullableExpTree) {
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...
298
            $resultExpression = $this->mergeNullableExpressionTrees(
299
                $leftNullableExpTree,
300
                $rightNullableExpTree
301
            );
302
        } else {
303
            $resultExpression = null != $leftNullableExpTree
304
                               ? $leftNullableExpTree : $rightNullableExpTree;
305
        }
306
307
        if (null == $resultExpression) {
308
            return null;
309
        }
310
311
        if (null == $parentExpression) {
312
            return new LogicalExpression(
313
                $resultExpression,
314
                $expression,
315
                ExpressionType::AND_LOGICAL
316
            );
317
        }
318
319
        return $resultExpression;
320
    }
321
322
    /**
323
     * Process an property access expression node for nullability.
324
     *
325
     * @param PropertyAccessExpression $expression            The property access
326
     *                                                        expression node to process
327
     * @param AbstractExpression       $parentExpression      The parent expression of
328
     *                                                        expression node to process
329
     * @param bool                     $checkNullForMostChild Wheter to check null for
330
     *                                                        most child node or not
331
     *
332
     * @return LogicalExpression|RelationalExpression|null
333
     */
334
    private function processPropertyAccessNode(
335
        PropertyAccessExpression $expression,
336
        $parentExpression,
337
        $checkNullForMostChild
338
    ) {
339
        $navigationsUsed = $expression->getNavigationPropertiesInThePath();
340
        if (!empty($navigationsUsed)) {
341
            $this->navigationPropertiesUsedInTheExpression[] = $navigationsUsed;
342
        }
343
344
        $nullableExpTree = $expression->createNullableExpressionTree($checkNullForMostChild);
345
346
        if (null == $parentExpression) {
347
            return new LogicalExpression(
348
                $nullableExpTree,
0 ignored issues
show
Bug introduced by
It seems like $nullableExpTree defined by $expression->createNulla...$checkNullForMostChild) on line 344 can also be of type null; however, POData\UriProcessor\Quer...pression::__construct() does only seem to accept object<POData\UriProcess...ons\AbstractExpression>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
349
                $expression,
350
                ExpressionType::AND_LOGICAL
351
            );
352
        }
353
354
        return $nullableExpTree;
355
    }
356
357
    /**
358
     * Process a releational expression node for nullability.
359
     *
360
     * @param RelationalExpression $expression       The relational expression node
361
     *                                               to process
362
     * @param AbstractExpression   $parentExpression The parent expression of
363
     *                                               expression node to process
364
     *
365
     * @return null|AbstractExpression
366
     */
367
    private function processRelationalNode(
368
        RelationalExpression $expression,
369
        $parentExpression
370
    ) {
371
        $leftNullableExpTree = $this->processNodeForNullability(
372
            $expression->getLeft(),
373
            $expression
374
        );
375
        $rightNullableExpTree = $this->processNodeForNullability(
376
            $expression->getRight(),
377
            $expression
378
        );
379
        $resultExpression = null;
0 ignored issues
show
Unused Code introduced by
$resultExpression 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...
380 View Code Duplication
        if (null != $leftNullableExpTree && null != $rightNullableExpTree) {
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...
381
            $resultExpression = $this->mergeNullableExpressionTrees(
382
                $leftNullableExpTree,
383
                $rightNullableExpTree
384
            );
385
        } else {
386
            $resultExpression = $leftNullableExpTree != null
387
                               ? $leftNullableExpTree : $rightNullableExpTree;
388
        }
389
390
        if (null == $resultExpression) {
391
            return null;
392
        }
393
394
        if (null == $parentExpression) {
395
            return new LogicalExpression(
396
                $resultExpression,
397
                $expression,
398
                ExpressionType::AND_LOGICAL
399
            );
400
        }
401
402
        return $resultExpression;
403
    }
404
405
    /**
406
     * Process an unary expression node for nullability.
407
     *
408
     * @param UnaryExpression    $expression       The unary expression node
409
     *                                             to process
410
     * @param AbstractExpression $parentExpression The parent expression of
411
     *                                             expression node to process
412
     *
413
     * @return AbstractExpression|null
414
     */
415
    private function processUnaryNode(
416
        UnaryExpression $expression,
417
        $parentExpression
418
    ) {
419
        if (ExpressionType::NEGATE == $expression->getNodeType()) {
420
            return $this->processNodeForNullability(
421
                $expression->getChild(),
422
                $expression
423
            );
424
        }
425
426
        if (ExpressionType::NOT_LOGICAL == $expression->getNodeType()) {
427
            $resultExpression = $this->processNodeForNullability(
428
                $expression->getChild(),
429
                $expression
430
            );
431
            if (null == $resultExpression) {
432
                return null;
433
            }
434
435
            if (null == $parentExpression) {
436
                return new LogicalExpression(
437
                    $resultExpression,
438
                    $expression,
439
                    ExpressionType::AND_LOGICAL
440
                );
441
            }
442
443
            return $resultExpression;
444
        }
445
446
        throw ODataException::createSyntaxError(
447
            Messages::expressionParser2UnexpectedExpression(get_class($expression))
448
        );
449
    }
450
451
    /**
452
     * Merge two null check expression trees by removing duplicate nodes.
453
     *
454
     * @param AbstractExpression $nullCheckExpTree1 First expression
455
     * @param AbstractExpression $nullCheckExpTree2 Second expression
456
     *
457
     * @return AbstractExpression
458
     */
459
    private function mergeNullableExpressionTrees(
460
        $nullCheckExpTree1,
461
        $nullCheckExpTree2
462
    ) {
463
        $this->mapTable = [];
464
        $this->map($nullCheckExpTree1);
465
        $this->map($nullCheckExpTree2);
466
        $expression = null;
467
        $isNullFunctionDescription = null;
0 ignored issues
show
Unused Code introduced by
$isNullFunctionDescription 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...
468
        foreach ($this->mapTable as $node) {
469
            if (null == $expression) {
470
                $expression = new UnaryExpression(
471
                    new FunctionCallExpression(
472
                        FunctionDescription::isNullCheckFunction($node->getType()),
473
                        [$node]
474
                    ),
475
                    ExpressionType::NOT_LOGICAL,
476
                    new Boolean()
477
                );
478
            } else {
479
                $expression = new LogicalExpression(
480
                    $expression,
481
                    new UnaryExpression(
482
                        new FunctionCallExpression(
483
                            FunctionDescription::isNullCheckFunction(
484
                                $node->getType()
485
                            ),
486
                            [$node]
487
                        ),
488
                        ExpressionType::NOT_LOGICAL,
489
                        new Boolean()
490
                    ),
491
                    ExpressionType::AND_LOGICAL
492
                );
493
            }
494
        }
495
496
        return $expression;
497
    }
498
499
    /**
500
     *  Populate map table.
501
     *
502
     * @param AbstractExpression $nullCheckExpTree The expression to verfiy
503
     *
504
     * @throws ODataException
505
     */
506
    private function map($nullCheckExpTree)
507
    {
508
        if ($nullCheckExpTree instanceof LogicalExpression) {
509
            $this->map($nullCheckExpTree->getLeft());
510
            $this->map($nullCheckExpTree->getRight());
511
        } elseif ($nullCheckExpTree instanceof UnaryExpression) {
512
            $this->map($nullCheckExpTree->getChild());
513
        } elseif ($nullCheckExpTree instanceof FunctionCallExpression) {
514
            $param = $nullCheckExpTree->getParamExpressions();
515
            $this->map($param[0]);
516
        } elseif ($nullCheckExpTree instanceof PropertyAccessExpression) {
517
            $parent = $nullCheckExpTree;
518
            $key = null;
519
            do {
520
                $key = $parent->getResourceProperty()->getName() . '_' . $key;
521
                $parent = $parent->getParent();
522
            } while (null != $parent);
523
524
            $this->mapTable[$key] = $nullCheckExpTree;
525
        } else {
526
            throw ODataException::createSyntaxError(
527
                Messages::expressionParser2UnexpectedExpression(get_class($nullCheckExpTree))
528
            );
529
        }
530
    }
531
}
532