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

NorthWindDSExpressionProvider   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 463
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 74
eloc 249
dl 0
loc 463
c 0
b 0
f 0
rs 2.48

12 Methods

Rating   Name   Duplication   Size   Complexity  
A onArithmeticExpression() 0 20 6
A onLogicalExpression() 0 11 3
C _prepareBinaryExpression() 0 53 13
A onUnaryExpression() 0 11 3
A onConstantExpression() 0 9 3
A __construct() 0 2 1
D onFunctionCallExpression() 0 100 27
B onRelationalExpression() 0 27 7
A setResourceType() 0 3 1
B onPropertyAccessExpression() 0 65 8
A getIteratorName() 0 3 1
A _prepareUnaryExpression() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like NorthWindDSExpressionProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use NorthWindDSExpressionProvider, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ExpressionType;
4
use POData\Providers\Metadata\Type\IType;
5
use POData\Common\NotImplementedException;
6
use POData\Common\ODataConstants;
7
use POData\Providers\Metadata\ResourcePropertyKind;
8
use POData\Providers\Metadata\ResourceType;
9
use POData\Providers\Expression\IExpressionProvider as IExpressionProvider;
10
11
12
class NorthWindDSExpressionProvider implements IExpressionProvider
13
{
14
    const ADD                  = '+';
15
    const CLOSE_BRACKET        = ')';
16
    const COMMA                = ',';
17
    const DIVIDE               = '/';
18
    const SUBTRACT             = '-';
19
    const EQUAL                = '=';
20
    const GREATERTHAN          = '>';
21
    const GREATERTHAN_OR_EQUAL = '>=';
22
    const LESSTHAN             = '<';
23
    const LESSTHAN_OR_EQUAL    = '<=';
24
    const LOGICAL_AND          = 'AND';
25
    const LOGICAL_NOT          = 'not';
26
    const LOGICAL_OR           = 'OR';
27
    const MEMBERACCESS         = '';
28
    const MODULO               = '%';
29
    const MULTIPLY             = '*';
30
    const NEGATE               = '-';
31
    const NOTEQUAL             = '!=';
32
    const OPEN_BRAKET          = '(';
33
    // The default parameter for ROUND sql function-call
34
    private $_default_round = 0;
35
36
    /**
37
     * The name of iterator
38
     * 
39
     * @var string
40
     */
41
    private $_iterName;
0 ignored issues
show
introduced by
The private property $_iterName is not used, and could be removed.
Loading history...
42
43
    /**
44
     * The type of the resource pointed by the resource path segement
45
     *
46
     * @var ResourceType
47
     */
48
    private $_resourceType;
49
50
    /**
51
     * Constructs new instance of SQLSrverExpressionProvider for NorthWind DB
52
     * 
53
     */
54
    public function __construct()
55
    {
56
    }
57
58
    /**
59
     * Get the name of the iterator
60
     * 
61
     * @return string
62
     */
63
    public function getIteratorName()
64
    {
65
        return null;
66
    }
67
68
    /**
69
     * call-back for setting the resource type.
70
     *
71
     * @param ResourceType $resourceType The resource type on which the filter
72
     *                                   is going to be applied.
73
     */
74
    public function setResourceType(ResourceType $resourceType)
75
    {
76
        $this->_resourceType = $resourceType;
77
    }
78
79
    /**
80
     * Call-back for logical expression
81
     * 
82
     * @param ExpressionType $expressionType The type of logical expression.
83
     * @param string         $left           The left expression.
84
     * @param string         $right          The left expression.
85
     * 
86
     * @return string
87
     */
88
    public function onLogicalExpression($expressionType, $left, $right)
89
    {
90
        switch ($expressionType) {
91
            case ExpressionType::AND_LOGICAL:
92
                return $this->_prepareBinaryExpression(self::LOGICAL_AND, $left, $right);
93
                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...
94
            case ExpressionType::OR_LOGICAL:
95
                return $this->_prepareBinaryExpression(self::LOGICAL_OR, $left, $right);
96
                break;
97
            default:
98
                throw new \InvalidArgumentException('onLogicalExpression');
99
        }
100
    }
101
102
    /**
103
     * Call-back for arithmetic expression
104
     * 
105
     * @param ExpressionType $expressionType The type of arithmetic expression.
106
     * @param string         $left           The left expression.
107
     * @param string         $right          The left expression.
108
     * 
109
     * @return string
110
     */
111
    public function onArithmeticExpression($expressionType, $left, $right)
112
    {
113
        switch ($expressionType) {
114
            case ExpressionType::MULTIPLY:
115
                return $this->_prepareBinaryExpression(self::MULTIPLY, $left, $right);
116
                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...
117
            case ExpressionType::DIVIDE:
118
                return $this->_prepareBinaryExpression(self::DIVIDE, $left, $right);
119
                break;
120
            case ExpressionType::MODULO:
121
                return $this->_prepareBinaryExpression(self::MODULO, $left, $right);
122
                break;
123
            case ExpressionType::ADD:
124
                return $this->_prepareBinaryExpression(self::ADD, $left, $right);
125
                break;
126
            case ExpressionType::SUBTRACT:
127
                return $this->_prepareBinaryExpression(self::SUBTRACT, $left, $right);
128
                break;
129
            default:
130
                throw new \InvalidArgumentException('onArithmeticExpression');
131
        }
132
    }
133
134
    /**
135
     * Call-back for relational expression
136
     * 
137
     * @param ExpressionType $expressionType The type of relation expression
138
     * @param string         $left           The left expression
139
     * @param string         $right          The left expression
140
     * 
141
     * @return string
142
     */
143
    public function onRelationalExpression($expressionType, $left, $right)
144
    {
145
        switch ($expressionType) {
146
            case ExpressionType::GREATERTHAN:
147
                return $this->_prepareBinaryExpression(self::GREATERTHAN, $left, $right);
148
                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...
149
            case ExpressionType::GREATERTHAN_OR_EQUAL:
150
                return $this->_prepareBinaryExpression(
151
                    self::GREATERTHAN_OR_EQUAL, $left, $right
152
                );
153
                break;
154
            case ExpressionType::LESSTHAN:
155
                return $this->_prepareBinaryExpression(self::LESSTHAN, $left, $right);
156
                break;
157
            case ExpressionType::LESSTHAN_OR_EQUAL:
158
                return $this->_prepareBinaryExpression(
159
                    self::LESSTHAN_OR_EQUAL, $left, $right
160
                );
161
                break;
162
            case ExpressionType::EQUAL:
163
                return $this->_prepareBinaryExpression(self::EQUAL, $left, $right);
164
                break;
165
            case ExpressionType::NOTEQUAL:
166
                return $this->_prepareBinaryExpression(self::NOTEQUAL, $left, $right);
167
                break;
168
            default:
169
                throw new \InvalidArgumentException('onArithmeticExpression');
170
        }
171
    }
172
173
    /**
174
     * Call-back for unary expression
175
     * 
176
     * @param ExpressionType $expressionType The type of unary expression
177
     * @param string         $child          The child expression
178
     * 
179
     * @return string
180
     */
181
    public function onUnaryExpression($expressionType, $child)
182
    {
183
        switch ($expressionType) {
184
            case ExpressionType::NEGATE:
185
                return $this->_prepareUnaryExpression(self::NEGATE, $child);
186
                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...
187
            case ExpressionType::NOT_LOGICAL:
188
                return $this->_prepareUnaryExpression(self::LOGICAL_NOT, $child);
189
                break;
190
            default:
191
                throw new \InvalidArgumentException('onUnaryExpression');
192
        }
193
    }
194
195
    /**
196
     * Call-back for constant expression
197
     * 
198
     * @param IType  $type  The type of constant
199
     * @param objetc $value The value of the constant
0 ignored issues
show
Bug introduced by
The type objetc was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
200
     * 
201
     * @return string
202
     */
203
    public function onConstantExpression(IType $type, $value)
204
    {    	
205
        if (is_bool($value)) {
0 ignored issues
show
introduced by
The condition is_bool($value) is always false.
Loading history...
206
            return var_export($value, true);
207
        } else if (is_null($value)) {
208
            return var_export(null, true);
209
        }
210
211
        return $value;
212
    }
213
214
    /**
215
     * Call-back for property access expression
216
     * 
217
     * @param PropertyAccessExpression $expression The property access expression
0 ignored issues
show
Bug introduced by
The type PropertyAccessExpression was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
218
     * 
219
     * @return string
220
     */
221
    public function onPropertyAccessExpression($expression)
222
    {
223
        $parent = $expression;
224
        $variable = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $variable is dead and can be removed.
Loading history...
225
        $isFirstLevelPrimitive = is_null($parent->getParent());
226
        if (!$isFirstLevelPrimitive) {
227
            // This propery access sub-expression in the $filter need access
228
            // to level 2 or greater property of a complex or resource reference
229
            // property.
230
            // e.g. Customers?$filter=Address/City eq 'Kottayam'
231
            //          Level_2 property access [Complex]
232
            //      Customers?$filter=Address/AltAddress/City eq 'Seattle'
233
            //          Level_3 property access [Complex]
234
            //      Orders?$filter=Customer/CustomerID eq 'ALFKI'
235
            //          Level_2 property access [Resource Reference]
236
            $parent2 = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $parent2 is dead and can be removed.
Loading history...
237
            do {
238
                $parent2 = $parent;
239
                $parent = $parent->getParent();
240
            } while ($parent != null);
241
        	
242
            $resourceProperty = $parent2->getResourceProperty();
243
            if ($resourceProperty->isKindOf(ResourcePropertyKind::RESOURCE_REFERENCE)) {
244
                // Orders?$filter=Customer/CustomerID eq 'ALFKI'
245
                throw new NotImplementedException(
246
                        'This implementation not supports Resource reference in the filter',
247
                        500,
248
                        NULL
249
                );
250
            } else {
251
                // Customers?$filter=Address/AltAddress/City eq 'Seattle'
252
                // Customers?$filter=Address/City eq 'Seattle'
253
                $propertyName = $parent2->getResourceProperty()->getName();
254
                if ("Address" == $propertyName) {
255
                    $child = $parent2->getChild();
256
                    $propertyName = $child->getResourceProperty()->getName();
257
                    if ("AltAddress" != $propertyName) {
258
                        return $propertyName;
259
                    }
260
        			
261
                    throw new NotImplementedException(
262
                            'This implementation not supports Customer::Address::AltAddress in the filter',
263
                            500,
264
                            NULL
265
                    );
266
                }
267
            }
268
269
        } else {
270
            // This is a first level property access
271
            $resourceProperty = $parent->getResourceProperty();
272
            if ($resourceProperty->isKindOf(ResourcePropertyKind::COMPLEX_TYPE)
273
                || $resourceProperty->isKindOf(ResourcePropertyKind::RESOURCE_REFERENCE)) {
274
                // Customers?$filter=Address eq null
275
                // Orders?$filter=Customer ne null
276
                // First level property access to a complex or resource reference
277
                // which is not supported by $this [this implementation of IDSQP2]
278
                throw new NotImplementedException(
279
                    'First level complex and Resource reference are not supported in the filter', 
280
                    500, 
281
                    NULL
282
                );
283
            } else {
284
                // First level property access to primitive property
285
                return $parent->getResourceProperty()->getName();
286
            }
287
        }
288
    }
289
    /**
290
     * Call-back for function call expression
291
     * 
292
     * @param FunctionDescription $functionDescription Description of the function.
0 ignored issues
show
Bug introduced by
The type FunctionDescription was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
293
     * @param array<string>       $params              Paameters to the function.
294
     * 
295
     * @return string
296
     */
297
    public function onFunctionCallExpression($functionDescription, $params)
298
    {
299
        switch ($functionDescription->functionName) {
300
            case ODataConstants::STRFUN_COMPARE:
301
                return "STRCMP($params[0]; $params[1])";
302
                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...
303
            case ODataConstants::STRFUN_ENDSWITH:
304
              return "(($params[1]) = RIGHT(($params[0]), LEN($params[1])))";
305
                break;
306
            case ODataConstants::STRFUN_INDEXOF:
307
              // In SQLServer the index of string starts from 1, but in OData
308
              // the string start with index 0, so the below subtraction of 1
309
              return "(CHARINDEX($params[1], $params[0]) - 1)";
310
                break;
311
            case ODataConstants::STRFUN_REPLACE:
312
              return "REPLACE($params[0], $params[1], $params[2])";
313
                break;
314
            case ODataConstants::STRFUN_STARTSWITH:
315
              return "(($params[1]) = LEFT(($params[0]), LEN($params[1])))";
316
                break;
317
            case ODataConstants::STRFUN_TOLOWER:
318
              return "LOWER($params[0])";
319
                break;
320
            case ODataConstants::STRFUN_TOUPPER:
321
              return "UPPER($params[0])";
322
                break;
323
            case ODataConstants::STRFUN_TRIM:
324
              // OData supports trim function
325
              // We don't have the same function SQL Server, so use SQL functions LTRIM and RTRIM together
326
              // to achieve TRIM functionality.
327
              return "RTRIM(LTRIM($params[0]))";
328
                break;
329
            case ODataConstants::STRFUN_SUBSTRING:
330
              if (count($params) == 3) {
331
                // 3 Param version of OData substring
332
                    return "SUBSTRING($params[0], $params[1] + 1, $params[2])";
333
                } else {
334
                // 2 Params version of OData substring
335
                // We don't have the same function for SQL Server, we have only:
336
                //
337
                // SUBSTRING ( value_expression , start_expression , length_expression )
338
                // http://msdn.microsoft.com/en-us/library/ms187748.aspx
339
                //
340
                // If the sum of start_expression and length_expression is greater than the number of characters 
341
                // in value_expression, the whole value expression beginning at start_expression is returned
342
                // In OData substring function the index start from 0, in SQL Server its from 1
343
                return "SUBSTRING($params[0], $params[1] + 1, LEN($params[0]))";        	    
344
                }
345
                break;
346
            case ODataConstants::STRFUN_SUBSTRINGOF:
347
              return "(CHARINDEX($params[0], $params[1]) != 0)";
348
                break;
349
            case ODataConstants::STRFUN_CONCAT:
350
                return "$params[0] + $params[1]";
351
                break;
352
            case ODataConstants::STRFUN_LENGTH:
353
                return "LEN($params[0])";
354
                break;
355
            case ODataConstants::GUIDFUN_EQUAL:
356
                return "($params[0] = $params[1])";
357
                break;
358
            case ODataConstants::DATETIME_COMPARE:
359
                return "DATETIMECMP($params[0]; $params[1])";
360
                break;
361
            case ODataConstants::DATETIME_YEAR:
362
                return "YEAR($params[0])";
363
                break;
364
            case ODataConstants::DATETIME_MONTH:
365
                return "MONTH($params[0])";
366
                break;
367
            case ODataConstants::DATETIME_DAY:
368
                return "DAY($params[0])";
369
                break;
370
            case ODataConstants::DATETIME_HOUR:
371
                return "DATENAME(HOUR, $params[0])";
372
                break;
373
            case ODataConstants::DATETIME_MINUTE:
374
                return "DATENAME(MINUTE, $params[0])";
375
                break;
376
            case ODataConstants::DATETIME_SECOND:
377
                return "DATENAME(SECOND, $params[0])";
378
                break;                
379
            case ODataConstants::MATHFUN_ROUND:
380
                return "ROUND($params[0], $this->_default_round)";
381
                break;
382
            case ODataConstants::MATHFUN_CEILING:
383
                return "CEILING($params[0])";
384
                break;
385
            case ODataConstants::MATHFUN_FLOOR:
386
                return "FLOOR($params[0])";
387
                break;
388
            case ODataConstants::BINFUL_EQUAL:
389
                return "($params[0] = $params[1])";
390
                break;
391
            case 'is_null':
392
            return "is_null($params[0])";
393
            break;
394
395
            default:
396
                throw new \InvalidArgumentException('onFunctionCallExpression');
397
        }
398
    }
399
400
    /**
401
     * To format binary expression
402
     * 
403
     * @param string $operator The binary operator.
404
     * @param string $left     The left operand.
405
     * @param string $right    The right operand.
406
     * 
407
     * @return string
408
     */
409
    private function _prepareBinaryExpression($operator, $left, $right)
410
    {
411
        if (!substr_compare($left, "STRCMP", 0, 6)) {
412
            $str = explode(';', $left, 2);
413
            $str[0] = str_replace('STRCMP', '', $str[0]);
414
            if ($right == 'false' and $right != '0') {
415
                if (!substr_compare($operator, '!', 0, 1)) {
416
                    $operator = str_replace('!', '', $operator);
417
                } else if ($operator == '>=') {
418
                    $operator = '<';
419
                } else if ($operator == '<=') {
420
                    $operator = '>';
421
                } else {
422
                    $operator = "!" . $operator;
423
                }
424
                return self::OPEN_BRAKET 
425
                    . $str[0] . ' ' . $operator 
426
                    . ' ' . $str[1] . self::CLOSE_BRACKET;
427
            } else {
428
                return self::OPEN_BRAKET 
429
                    . $str[0] . ' ' . $operator 
430
                    . ' ' . $str[1] . self::CLOSE_BRACKET;
431
            }
432
        }
433
        
434
        //DATETIMECMP
435
        if (!substr_compare($left, "DATETIMECMP", 0, 11)) {
436
            $str = explode(';', $left, 2);
437
            $str[0] = str_replace('DATETIMECMP', '', $str[0]);
438
            if ($right == 'false' and $right != '0') {
439
            if (!substr_compare($operator, '!', 0, 1)) {
440
                $operator = str_replace('!', '', $operator);
441
            } else if ($operator == '>=') {
442
                $operator = '<';
443
            } else if ($operator == '<=') {
444
                $operator = '>';
445
            } else {
446
                $operator = "!" . $operator;
447
            }
448
            return self::OPEN_BRAKET
449
            . $str[0] . ' ' . $operator
450
            . ' ' . $str[1] . self::CLOSE_BRACKET;
451
            } else {
452
            return self::OPEN_BRAKET
453
            . $str[0] . ' ' . $operator
454
            . ' ' . $str[1] . self::CLOSE_BRACKET;
455
            }
456
        }
457
458
        return 
459
            self::OPEN_BRAKET 
460
            . $left . ' ' . $operator 
461
            . ' ' . $right . self::CLOSE_BRACKET;
462
    }
463
464
    /**
465
     * To format unary expression
466
     * 
467
     * @param string $operator The unary operator.
468
     * @param string $child    The operand.
469
     * 
470
     * @return string
471
     */
472
    private function _prepareUnaryExpression($operator, $child)
473
    {
474
        return $operator . self::OPEN_BRAKET . $child . self::CLOSE_BRACKET;
475
    }
476
}
477