Passed
Pull Request — master (#159)
by Alex
03:41
created

LaravelExpressionProvider::checkBlankString()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 12
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 12
ccs 0
cts 0
cp 0
rs 8.8571
cc 5
eloc 7
nc 4
nop 2
crap 30
1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Query;
4
5
use POData\Common\ODataConstants;
6
use POData\Providers\Expression\IExpressionProvider;
7
use POData\Providers\Metadata\ResourceType;
8
use POData\Providers\Metadata\Type\IType;
9
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ExpressionType;
10
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\PropertyAccessExpression;
11
12
class LaravelExpressionProvider implements IExpressionProvider
13
{
14
    const ADD = '+';
15
    const CLOSE_BRACKET = ')';
16
    const COMMA = ',';
17
    const DIVIDE = '/';
18
    const SUBTRACT = '-';
19
    const EQUAL = '==';
20
    const GREATER_THAN = '>';
21
    const GREATER_THAN_OR_EQUAL = '>=';
22
    const LESS_THAN = '<';
23
    const LESS_THAN_OR_EQUAL = '<=';
24
    const LOGICAL_AND = '&&';
25
    const LOGICAL_NOT = '!';
26
    const LOGICAL_OR = '||';
27
    const MEMBER_ACCESS = '->';
28
    const MODULO = '%';
29
    const MULTIPLY = '*';
30
    const NEGATE = '-';
31
    const NOT_EQUAL = '!=';
32
    const OPEN_BRACKET = '(';
33
    const TYPE_NAMESPACE = 'POData\\Providers\\Metadata\\Type\\';
34
35
    private $functionDescriptionParsers;
36
37
    /**
38
     * The name of iterator.
39
     *
40
     * @var string
41
     */
42
    private $iteratorName;
43
    /**
44
     * The type of the resource pointed by the resource path segment.
45
     *
46
     * @var ResourceType
47
     */
48
    private $resourceType;
49 52
50
    public function __construct()
51 52
    {
52 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::STRFUN_COMPARE] = function ($params) {
53
            return $this->checkEmptyString($params)
54
                ? 'true'
55
                : 'strcmp(' . $params[0] . ', ' . $params[1] . ')';
56
        };
57 1
        $this->functionDescriptionParsers[ODataConstants::STRFUN_ENDSWITH] = function ($params) {
58
            if ($this->checkEmptyString($params) || $this->checkBlankString($params, [1])) {
59 1
                return 'true';
60
            }
61
            return '(strcmp(substr(' . $params[0] . ', strlen(' . $params[0] . ') - strlen(' . $params[1] . ')), '
62
                   .$params[1] . ') === 0)';
63
        };
64
        $this->functionDescriptionParsers[ODataConstants::STRFUN_INDEXOF] = function ($params) {
65
            return $this->checkEmptyString($params) || $this->checkBlankString($params, [1])
66
                ? 'true'
67 1
                : 'strpos(' . $params[0] . ', ' . $params[1] . ')';
68
        };
69 1 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::STRFUN_REPLACE] = function ($params) {
70 1
            return 'str_replace(' . $params[1] . ', ' . $params[2] . ', ' . $params[0] . ')';
71 1
        };
72
        $this->functionDescriptionParsers[ODataConstants::STRFUN_STARTSWITH] = function ($params) {
73
            return $this->checkEmptyString($params) || $this->checkBlankString($params, [1])
74
                ? 'true'
75
                : '(strpos(' . $params[0] . ', ' . $params[1] . ') === 0)';
76
        };
77
        $this->functionDescriptionParsers[ODataConstants::STRFUN_TOLOWER] = function ($params) {
78
            return $this->checkEmptyString($params)
79
                ? 'true'
80
                : 'strtolower(' . $params[0] . ')';
81 3
        };
82
        $this->functionDescriptionParsers[ODataConstants::STRFUN_TOUPPER] = function ($params) {
83
            return $this->checkEmptyString($params)
84 3
                ? 'true'
85 1
                : 'strtoupper(' . $params[0] . ')';
86 2
        };
87 1
        $this->functionDescriptionParsers[ODataConstants::STRFUN_TRIM] = function ($params) {
88 1
            return $this->checkEmptyString($params)
89 1
                ? 'true'
90 1
                : 'trim(' . $params[0] . ')';
91
        };
92
        $this->functionDescriptionParsers[ODataConstants::STRFUN_SUBSTRING] = function ($params) {
93
            if ($this->checkEmptyString($params)) {
94
                return 'true';
95
            }
96
            return count($params) == 3 ?
97
                'substr(' . $params[0] . ', ' . $params[1] . ', ' . $params[2] . ')'
98
                : 'substr(' . $params[0] . ', ' . $params[1] . ')';
99
        };
100 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::STRFUN_SUBSTRINGOF] = function ($params) {
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...
101 6
            return $this->checkEmptyString($params)
102
                ? 'true'
103
                : '(strpos(' . $params[1] . ', ' . $params[0] . ') !== false)';
104 6
        };
105 1 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::STRFUN_CONCAT] = function ($params) {
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...
106 5
            return $this->checkEmptyString($params)
107 1
                ? 'true'
108 4
                : $params[0] . ' . ' . $params[1];
109 1
        };
110 3
        $this->functionDescriptionParsers[ODataConstants::STRFUN_LENGTH] = function ($params) {
111 1
            return $this->checkEmptyString($params)
112 2
                ? 'true'
113 1
                : 'strlen(' . $params[0] . ')';
114 1
        };
115 1 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::GUIDFUN_EQUAL] = function ($params) {
116 1
            return self::TYPE_NAMESPACE . 'Guid::guidEqual(' . $params[0] . ', ' . $params[1] . ')';
117
        };
118 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::DATETIME_COMPARE] = function ($params) {
119
            return $this->checkEmptyString($params)
120
                ? 'true'
121
                : self::TYPE_NAMESPACE . 'DateTime::dateTimeCmp(' . $params[0] . ', ' . $params[1] . ')';
122
        };
123 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::DATETIME_YEAR] = function ($params) {
1 ignored issue
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...
124
            return $this->checkEmptyString($params)
125
                ? 'true'
126
                : self::TYPE_NAMESPACE . 'DateTime::year(' . $params[0] . ')';
127 7
        };
128 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::DATETIME_MONTH] = function ($params) {
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...
129
            return $this->checkEmptyString($params)
130 7
                ? 'true'
131 1
                : self::TYPE_NAMESPACE . 'DateTime::month(' . $params[0] . ')';
132 6
        };
133 1 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::DATETIME_DAY] = function ($params) {
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...
134 5
            return $this->checkEmptyString($params)
135 1
                ? 'true'
136 4
                : self::TYPE_NAMESPACE . 'DateTime::day(' . $params[0] . ')';
137 1
        };
138 3 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::DATETIME_HOUR] = function ($params) {
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...
139 1
            return $this->checkEmptyString($params)
140 2
                ? 'true'
141 1
                : self::TYPE_NAMESPACE . 'DateTime::hour(' . $params[0] . ')';
142 1
        };
143 1 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::DATETIME_MINUTE] = function ($params) {
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...
144 1
            return $this->checkEmptyString($params)
145
                ? 'true'
146
                : self::TYPE_NAMESPACE . 'DateTime::minute(' . $params[0] . ')';
147
        };
148 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::DATETIME_SECOND] = function ($params) {
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...
149
            return $this->checkEmptyString($params)
150
                ? 'true'
151
                : self::TYPE_NAMESPACE . 'DateTime::second(' . $params[0] . ')';
152
        };
153
        $this->functionDescriptionParsers[ODataConstants::MATHFUN_ROUND] = function ($params) {
154 3
            return $this->checkEmptyString($params)
155
                ? 'true'
156
                : 'round(' . $params[0] . ')';
157 3
        };
158 1
        $this->functionDescriptionParsers[ODataConstants::MATHFUN_CEILING] = function ($params) {
159 2
            return $this->checkEmptyString($params)
160 1
                ? 'true'
161 1
                : 'ceil(' . $params[0] . ')';
162 1
        };
163 1
        $this->functionDescriptionParsers[ODataConstants::MATHFUN_FLOOR] = function ($params) {
164
            return $this->checkEmptyString($params)
165
                ? 'true'
166
                : 'floor(' . $params[0] . ')';
167
        };
168 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::BINFUL_EQUAL] = function ($params) {
1 ignored issue
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...
169
            return self::TYPE_NAMESPACE . 'Binary::binaryEqual(' . $params[0] . ', ' . $params[1] . ')';
170
        };
171
        $this->functionDescriptionParsers['is_null'] = function ($params) {
172
            return $this->checkEmptyString($params)
173 8
                ? 'true'
174
                : 'is_null(' . $params[0] . ')';
175 8
        };
176 2
    }
177 6
    /**
178 1
     * Get the name of the iterator.
179
     *
180 5
     * @return string
181
     */
182
    public function getIteratorName()
183
    {
184
        return $this->iteratorName;
185
    }
186
187
    /**
188
     * Get the resource type.
189 1
     *
190
     * @return object|null
191 1
     */
192 1
    public function getResourceType()
193
    {
194 1
        return $this->resourceType;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->resourceType returns the type POData\Providers\Metadata\ResourceType which is incompatible with the documented return type object|null.
Loading history...
195 1
    }
196 1
197 1
    /**
198 1
     * call-back for setting the resource type.
199 1
     *
200
     * @param ResourceType $resourceType The resource type on which the filter
201
     *                                   is going to be applied
202
     */
203
    public function setResourceType(ResourceType $resourceType)
204
    {
205
        $this->iteratorName = '$' . $resourceType->getName();
206
        $this->resourceType = $resourceType;
207
    }
208
    /**
209 28
     * Call-back for logical expression.
210
     *
211 28
     * @param ExpressionType $expressionType The type of logical expression
212 1
     * @param string         $left           The left expression
213
     * @param string         $right          The left expression
214 27
     *
215 27
     * @return string
216 1
     */
217 26 View Code Duplication
    public function onLogicalExpression($expressionType, $left, $right)
218 1
    {
219 25
        $type = $this->unpackExpressionType($expressionType);
220 1
        switch ($type) {
221 24
            case ExpressionType::AND_LOGICAL:
222 1
                return $this->prepareBinaryExpression(self::LOGICAL_AND, $left, $right);
223 23
            case ExpressionType::OR_LOGICAL:
224 1
                return $this->prepareBinaryExpression(self::LOGICAL_OR, $left, $right);
225 22
            default:
226 1
                throw new \InvalidArgumentException('onLogicalExpression');
227 21
        }
228 1
    }
229 20
    /**
230 1
     * Call-back for arithmetic expression.
231 19
     *
232 2
     * @param ExpressionType $expressionType The type of arithmetic expression
233 2
     * @param string         $left           The left expression
234 17
     * @param string         $right          The left expression
235 1
     *
236 16
     * @return string
237 1
     */
238 15
    public function onArithmeticExpression($expressionType, $left, $right)
239 1
    {
240 14
        $type = $this->unpackExpressionType($expressionType);
241 1
        switch ($type) {
242 13
            case ExpressionType::MULTIPLY:
243 1
                return $this->prepareBinaryExpression(self::MULTIPLY, $left, $right);
244 12
            case ExpressionType::DIVIDE:
245 1
                return $this->prepareBinaryExpression(self::DIVIDE, $left, $right);
246 11
            case ExpressionType::MODULO:
247 1
                return $this->prepareBinaryExpression(self::MODULO, $left, $right);
248 10
            case ExpressionType::ADD:
249 1
                return $this->prepareBinaryExpression(self::ADD, $left, $right);
250 9
            case ExpressionType::SUBTRACT:
251 1
                return $this->prepareBinaryExpression(self::SUBTRACT, $left, $right);
252 8
            default:
253 1
                throw new \InvalidArgumentException('onArithmeticExpression');
254 7
        }
255 1
    }
256 6
    /**
257 1
     * Call-back for relational expression.
258 5
     *
259 1
     * @param ExpressionType $expressionType The type of relation expression
260 4
     * @param string         $left           The left expression
261 1
     * @param string         $right          The left expression
262 3
     *
263 1
     * @return string
264 2
     */
265 1
    public function onRelationalExpression($expressionType, $left, $right)
266 1
    {
267 1
        $type = $this->unpackExpressionType($expressionType);
268 1
        switch ($type) {
269
            case ExpressionType::GREATERTHAN:
270
                return $this->prepareBinaryExpression(self::GREATER_THAN, $left, $right);
271
            case ExpressionType::GREATERTHAN_OR_EQUAL:
272
                return $this->prepareBinaryExpression(self::GREATER_THAN_OR_EQUAL, $left, $right);
273
            case ExpressionType::LESSTHAN:
274
                return $this->prepareBinaryExpression(self::LESS_THAN, $left, $right);
275
            case ExpressionType::LESSTHAN_OR_EQUAL:
276
                return $this->prepareBinaryExpression(self::LESS_THAN_OR_EQUAL, $left, $right);
277
            case ExpressionType::EQUAL:
278
                return $this->prepareBinaryExpression(self::EQUAL, $left, $right);
279 13
            case ExpressionType::NOTEQUAL:
280
                return $this->prepareBinaryExpression(self::NOT_EQUAL, $left, $right);
281
            default:
282 13
                throw new \InvalidArgumentException('onRelationalExpression');
283
        }
284
    }
285
    /**
286
     * Call-back for unary expression.
287
     *
288
     * @param ExpressionType $expressionType The type of unary expression
289
     * @param string         $child          The child expression
290
     *
291
     * @return string
292 2
     */
293 View Code Duplication
    public function onUnaryExpression($expressionType, $child)
294 2
    {
295
        $type = $this->unpackExpressionType($expressionType);
296
        switch ($type) {
297
            case ExpressionType::NEGATE:
298
                return $this->prepareUnaryExpression(self::NEGATE, $child);
299
            case ExpressionType::NOT_LOGICAL:
300
                return $this->prepareUnaryExpression(self::LOGICAL_NOT, $child);
301
            default:
302
                throw new \InvalidArgumentException('onUnaryExpression');
303
        }
304
    }
305
    /**
306
     * Call-back for constant expression.
307
     *
308
     * @param IType $type  The type of constant
309
     * @param mixed $value The value of the constant
310
     *
311
     * @return string
312
     */
313
    public function onConstantExpression(IType $type, $value)
314
    {
315
        if (is_bool($value)) {
316
            return var_export($value, true);
317
        } elseif (null === $value) {
318
            return var_export(null, true);
319
        }
320
        return $value;
321
    }
322
    /**
323
     * Call-back for property access expression.
324
     *
325
     * @param PropertyAccessExpression $expression The property access expression
326
     *
327
     * @return string
328
     */
329
    public function onPropertyAccessExpression($expression)
330
    {
331
        $parent = $expression;
332
        $variable = null;
333
        do {
334
            $variable = $parent->getResourceProperty()->getName() . self::MEMBER_ACCESS . $variable;
335
            $parent = $parent->getParent();
336
        } while ($parent != null);
337
        $variable = rtrim($variable, self::MEMBER_ACCESS);
338
        $variable = $this->getIteratorName() . self::MEMBER_ACCESS . $variable;
339
        return $variable;
340
    }
341
    /**
342
     * Call-back for function call expression.
343
     *
344
     * @param \POData\UriProcessor\QueryProcessor\FunctionDescription $functionDescription Description of the function
345
     * @param array<string>                                           $params              Parameters to the function
346
     *
347
     * @return string
348
     */
349
    public function onFunctionCallExpression($functionDescription, $params)
350
    {
351
        if (!isset($functionDescription)) {
352
            throw new \InvalidArgumentException('onFunctionCallExpression');
353
        }
354
        if (!array_key_exists($functionDescription->name, $this->functionDescriptionParsers)) {
355
            throw new \InvalidArgumentException('onFunctionCallExpression');
356
        }
357
        return $this->functionDescriptionParsers[$functionDescription->name]($params);
358
    }
359
    /**
360
     * To format binary expression.
361
     *
362
     * @param string $operator The binary operator
363
     * @param string $left     The left operand
364
     * @param string $right    The right operand
365
     *
366
     * @return string
367
     */
368
    private function prepareBinaryExpression($operator, $left, $right)
369
    {
370
        return self::OPEN_BRACKET . $left . ' ' . $operator . ' ' . $right . self::CLOSE_BRACKET;
371
    }
372
    /**
373
     * To format unary expression.
374
     *
375
     * @param string $operator The unary operator
376
     * @param string $child    The operand
377
     *
378
     * @return string
379
     */
380
    private function prepareUnaryExpression($operator, $child)
381
    {
382
        return $operator . self::OPEN_BRACKET . $child . self::CLOSE_BRACKET;
383
    }
384
385
    /**
386
     * @param $expressionType
387
     * @return mixed
388
     */
389
    private function unpackExpressionType($expressionType)
390
    {
391
        if ($expressionType instanceof ExpressionType) {
392
            $type = $expressionType->getValue();
393
        } else {
394
            $type = $expressionType;
395
        }
396
        return $type;
397
    }
398
399
    private function checkEmptyString(array $parms)
400
    {
401
        foreach ($parms as $string) {
402
            if ('' == $string) {
403
                return true;
404
            }
405
            // to catch strings that aren't themselves wrapping strings - if this is missed, then run massive
406
            // risk of referencing undefined constants
407
            if (is_string($string)) {
408
                if (ltrim($string, '\'\"') == $string || rtrim($string, '\'\"') == $string) {
409
                    return true;
410
                }
411
            }
412
        }
413
        return false;
414
    }
415
416
    private function checkBlankString(array $parms, array $indices)
417
    {
418
        foreach ($indices as $index) {
419
            if (!array_key_exists($index, $parms)) {
420
                return true;
421
            }
422
            $parm = $parms[$index];
423
            if ('""' == $parm || "''" == $parm) {
424
                return true;
425
            }
426
        }
427
        return false;
428
    }
429
}
430