Completed
Pull Request — master (#84)
by Alex
12:11
created

prepareUnaryExpression()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 2
crap 2
1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Query;
4
5
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\ExpressionType;
6
use POData\Providers\Metadata\Type\IType;
7
use POData\Common\ODataConstants;
8
use POData\Providers\Metadata\ResourceType;
9
use POData\UriProcessor\QueryProcessor\ExpressionParser\Expressions\PropertyAccessExpression;
10
use POData\UriProcessor\QueryProcessor\FunctionDescription;
11
use POData\Providers\Expression\IExpressionProvider;
12
13
class LaravelExpressionProvider implements IExpressionProvider
14
{
15
    const ADD = '+';
16
    const CLOSE_BRACKET = ')';
17
    const COMMA = ',';
18
    const DIVIDE = '/';
19
    const SUBTRACT = '-';
20
    const EQUAL = '==';
21
    const GREATER_THAN = '>';
22
    const GREATER_THAN_OR_EQUAL = '>=';
23
    const LESS_THAN = '<';
24
    const LESS_THAN_OR_EQUAL = '<=';
25
    const LOGICAL_AND = '&&';
26
    const LOGICAL_NOT = '!';
27
    const LOGICAL_OR = '||';
28
    const MEMBER_ACCESS = '->';
29
    const MODULO = '%';
30
    const MULTIPLY = '*';
31
    const NEGATE = '-';
32
    const NOT_EQUAL = '!=';
33
    const OPEN_BRACKET = '(';
34
    const TYPE_NAMESPACE = 'POData\\Providers\\Metadata\\Type\\';
35
36
    private $functionDescriptionParsers;
37
38
    /**
39
     * The name of iterator.
40
     *
41
     * @var string
42
     */
43
    private $iteratorName;
44
    /**
45
     * The type of the resource pointed by the resource path segment.
46
     *
47
     * @var ResourceType
48
     */
49 52
    private $resourceType;
50
    /**
51 52
     */
52
    public function __construct()
53
    {
54
        $this->functionDescriptionParsers[ODataConstants::STRFUN_COMPARE] = function ($params) {
55
            return 'strcmp('.$params[0].', '.$params[1].')';
56
        };
57 1
        $this->functionDescriptionParsers[ODataConstants::STRFUN_ENDSWITH] = function ($params) {
58
            return '(strcmp(substr('.$params[0].', strlen('.$params[0].') - strlen('.$params[1].')), '
59 1
                   .$params[1].') === 0)';
60
        };
61
        $this->functionDescriptionParsers[ODataConstants::STRFUN_INDEXOF] = function ($params) {
62
            return 'strpos('.$params[0].', '.$params[1].')';
63
        };
64 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::STRFUN_REPLACE] = 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...
65
            return 'str_replace('.$params[1].', '.$params[2].', '.$params[0].')';
66
        };
67 1
        $this->functionDescriptionParsers[ODataConstants::STRFUN_STARTSWITH] = function ($params) {
68
            return '(strpos('.$params[0].', '.$params[1].') === 0)';
69 1
        };
70 1
        $this->functionDescriptionParsers[ODataConstants::STRFUN_TOLOWER] = function ($params) {
71 1
            return 'strtolower('.$params[0].')';
72
        };
73
        $this->functionDescriptionParsers[ODataConstants::STRFUN_TOUPPER] = function ($params) {
74
            return 'strtoupper('.$params[0].')';
75
        };
76
        $this->functionDescriptionParsers[ODataConstants::STRFUN_TRIM] = function ($params) {
77
            return 'trim('.$params[0].')';
78
        };
79
        $this->functionDescriptionParsers[ODataConstants::STRFUN_SUBSTRING] = function ($params) {
80
            return count($params) == 3 ?
81 3
                'substr('.$params[0].', '.$params[1].', '.$params[2].')' : 'substr('.$params[0].', '.$params[1].')';
82
        };
83
        $this->functionDescriptionParsers[ODataConstants::STRFUN_SUBSTRINGOF] = function ($params) {
84 3
            return '(strpos('.$params[1].', '.$params[0].') !== false)';
85 1
        };
86 2
        $this->functionDescriptionParsers[ODataConstants::STRFUN_CONCAT] = function ($params) {
87 1
            return $params[0].' . '.$params[1];
88 1
        };
89 1
        $this->functionDescriptionParsers[ODataConstants::STRFUN_LENGTH] = function ($params) {
90 1
            return 'strlen('.$params[0].')';
91
        };
92 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::GUIDFUN_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...
93
            return self::TYPE_NAMESPACE.'Guid::guidEqual('.$params[0].', '.$params[1].')';
94
        };
95 View Code Duplication
        $this->functionDescriptionParsers[ODataConstants::DATETIME_COMPARE] = 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...
96
            return self::TYPE_NAMESPACE.'DateTime::dateTimeCmp('.$params[0].', '.$params[1].')';
97
        };
98
        $this->functionDescriptionParsers[ODataConstants::DATETIME_YEAR] = function ($params) {
99
            return self::TYPE_NAMESPACE.'DateTime::year('.$params[0].')';
100
        };
101 6
        $this->functionDescriptionParsers[ODataConstants::DATETIME_MONTH] = function ($params) {
102
            return self::TYPE_NAMESPACE.'DateTime::month('.$params[0].')';
103
        };
104 6
        $this->functionDescriptionParsers[ODataConstants::DATETIME_DAY] = function ($params) {
105 1
            return self::TYPE_NAMESPACE.'DateTime::day('.$params[0].')';
106 5
        };
107 1
        $this->functionDescriptionParsers[ODataConstants::DATETIME_HOUR] = function ($params) {
108 4
            return self::TYPE_NAMESPACE.'DateTime::hour('.$params[0].')';
109 1
        };
110 3
        $this->functionDescriptionParsers[ODataConstants::DATETIME_MINUTE] = function ($params) {
111 1
            return self::TYPE_NAMESPACE.'DateTime::minute('.$params[0].')';
112 2
        };
113 1
        $this->functionDescriptionParsers[ODataConstants::DATETIME_SECOND] = function ($params) {
114 1
            return self::TYPE_NAMESPACE.'DateTime::second('.$params[0].')';
115 1
        };
116 1
        $this->functionDescriptionParsers[ODataConstants::MATHFUN_ROUND] = function ($params) {
117
            return 'round('.$params[0].')';
118
        };
119
        $this->functionDescriptionParsers[ODataConstants::MATHFUN_CEILING] = function ($params) {
120
            return 'ceil('.$params[0].')';
121
        };
122
        $this->functionDescriptionParsers[ODataConstants::MATHFUN_FLOOR] = function ($params) {
123
            return 'floor('.$params[0].')';
124
        };
125 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...
126
            return self::TYPE_NAMESPACE.'Binary::binaryEqual('.$params[0].', '.$params[1].')';
127 7
        };
128
        $this->functionDescriptionParsers['is_null'] = function ($params) {
129
            return 'is_null('.$params[0].')';
130 7
        };
131 1
    }
132 6
    /**
133 1
     * Get the name of the iterator.
134 5
     *
135 1
     * @return string
136 4
     */
137 1
    public function getIteratorName()
138 3
    {
139 1
        return $this->iteratorName;
140 2
    }
141 1
142 1
    /**
143 1
     * Get the resource type
144 1
     *
145
     * @return object|null
146
     */
147
    public function getResourceType()
148
    {
149
        return $this->resourceType;
150
    }
151
152
    /**
153
     * call-back for setting the resource type.
154 3
     *
155
     * @param ResourceType $resourceType The resource type on which the filter
156
     *                                   is going to be applied
157 3
     */
158 1
    public function setResourceType(ResourceType $resourceType)
159 2
    {
160 1
        $this->iteratorName = '$'.$resourceType->getName();
161 1
        $this->resourceType = $resourceType;
162 1
    }
163 1
    /**
164
     * Call-back for logical expression.
165
     *
166
     * @param ExpressionType $expressionType The type of logical expression
167
     * @param string         $left           The left expression
168
     * @param string         $right          The left expression
169
     *
170
     * @return string
171
     */
172
    public function onLogicalExpression($expressionType, $left, $right)
173 8
    {
174
        switch ($expressionType) {
175 8
            case ExpressionType::AND_LOGICAL:
176 2
                return $this->prepareBinaryExpression(self::LOGICAL_AND, $left, $right);
177 6
            case ExpressionType::OR_LOGICAL:
178 1
                return $this->prepareBinaryExpression(self::LOGICAL_OR, $left, $right);
179
            default:
180 5
                throw new \InvalidArgumentException('onLogicalExpression');
181
        }
182
    }
183
    /**
184
     * Call-back for arithmetic expression.
185
     *
186
     * @param ExpressionType $expressionType The type of arithmetic expression
187
     * @param string         $left           The left expression
188
     * @param string         $right          The left expression
189 1
     *
190
     * @return string
191 1
     */
192 1
    public function onArithmeticExpression($expressionType, $left, $right)
193
    {
194 1
        switch ($expressionType) {
195 1
            case ExpressionType::MULTIPLY:
196 1
                return $this->prepareBinaryExpression(self::MULTIPLY, $left, $right);
197 1
            case ExpressionType::DIVIDE:
198 1
                return $this->prepareBinaryExpression(self::DIVIDE, $left, $right);
199 1
            case ExpressionType::MODULO:
200
                return $this->prepareBinaryExpression(self::MODULO, $left, $right);
201
            case ExpressionType::ADD:
202
                return $this->prepareBinaryExpression(self::ADD, $left, $right);
203
            case ExpressionType::SUBTRACT:
204
                return $this->prepareBinaryExpression(self::SUBTRACT, $left, $right);
205
            default:
206
                throw new \InvalidArgumentException('onArithmeticExpression');
207
        }
208
    }
209 28
    /**
210
     * Call-back for relational expression.
211 28
     *
212 1
     * @param ExpressionType $expressionType The type of relation expression
213
     * @param string         $left           The left expression
214 27
     * @param string         $right          The left expression
215 27
     *
216 1
     * @return string
217 26
     */
218 1
    public function onRelationalExpression($expressionType, $left, $right)
219 25
    {
220 1
        switch ($expressionType) {
221 24
            case ExpressionType::GREATERTHAN:
222 1
                return $this->prepareBinaryExpression(self::GREATER_THAN, $left, $right);
223 23
            case ExpressionType::GREATERTHAN_OR_EQUAL:
224 1
                return $this->prepareBinaryExpression(self::GREATER_THAN_OR_EQUAL, $left, $right);
225 22
            case ExpressionType::LESSTHAN:
226 1
                return $this->prepareBinaryExpression(self::LESS_THAN, $left, $right);
227 21
            case ExpressionType::LESSTHAN_OR_EQUAL:
228 1
                return $this->prepareBinaryExpression(self::LESS_THAN_OR_EQUAL, $left, $right);
229 20
            case ExpressionType::EQUAL:
230 1
                return $this->prepareBinaryExpression(self::EQUAL, $left, $right);
231 19
            case ExpressionType::NOTEQUAL:
232 2
                return $this->prepareBinaryExpression(self::NOT_EQUAL, $left, $right);
233 2
            default:
234 17
                throw new \InvalidArgumentException('onRelationalExpression');
235 1
        }
236 16
    }
237 1
    /**
238 15
     * Call-back for unary expression.
239 1
     *
240 14
     * @param ExpressionType $expressionType The type of unary expression
241 1
     * @param string         $child          The child expression
242 13
     *
243 1
     * @return string
244 12
     */
245 1
    public function onUnaryExpression($expressionType, $child)
246 11
    {
247 1
        switch ($expressionType) {
248 10
            case ExpressionType::NEGATE:
249 1
                return $this->prepareUnaryExpression(self::NEGATE, $child);
250 9
            case ExpressionType::NOT_LOGICAL:
251 1
                return $this->prepareUnaryExpression(self::LOGICAL_NOT, $child);
252 8
            default:
253 1
                throw new \InvalidArgumentException('onUnaryExpression');
254 7
        }
255 1
    }
256 6
    /**
257 1
     * Call-back for constant expression.
258 5
     *
259 1
     * @param IType $type  The type of constant
260 4
     * @param mixed $value The value of the constant
261 1
     *
262 3
     * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|object|integer|double|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
263 1
     */
264 2
    public function onConstantExpression(IType $type, $value)
265 1
    {
266 1
        if (is_bool($value)) {
267 1
            return var_export($value, true);
268 1
        } elseif (is_null($value)) {
269
            return var_export(null, true);
270
        }
271
        return $value;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $value; (object|integer|double|string|array) is incompatible with the return type declared by the interface POData\Providers\Express...r::onConstantExpression of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
272
    }
273
    /**
274
     * Call-back for property access expression.
275
     *
276
     * @param PropertyAccessExpression $expression The property access expression
277
     *
278
     * @return string
279 13
     */
280
    public function onPropertyAccessExpression($expression)
281
    {
282 13
        $parent = $expression;
283
        $variable = null;
284
        do {
285
            $variable = $parent->getResourceProperty()->getName().self::MEMBER_ACCESS.$variable;
286
            $parent = $parent->getParent();
287
        } while ($parent != null);
288
        $variable = rtrim($variable, self::MEMBER_ACCESS);
289
        $variable = $this->getIteratorName().self::MEMBER_ACCESS.$variable;
290
        return $variable;
291
    }
292 2
    /**
293
     * Call-back for function call expression.
294 2
     *
295
     * @param \POData\UriProcessor\QueryProcessor\FunctionDescription $functionDescription Description of the function
296
     * @param array<string>                                           $params              Parameters to the function
297
     *
298
     * @return string
299
     */
300
    public function onFunctionCallExpression($functionDescription, $params)
301
    {
302
        if (!isset($functionDescription)) {
303
            throw new \InvalidArgumentException('onFunctionCallExpression');
304
        }
305
        if (!array_key_exists($functionDescription->name, $this->functionDescriptionParsers)) {
306
            throw new \InvalidArgumentException('onFunctionCallExpression');
307
        }
308
        return $this->functionDescriptionParsers[$functionDescription->name]($params);
309
    }
310
    /**
311
     * To format binary expression.
312
     *
313
     * @param string $operator The binary operator
314
     * @param string $left     The left operand
315
     * @param string $right    The right operand
316
     *
317
     * @return string
318
     */
319
    private function prepareBinaryExpression($operator, $left, $right)
320
    {
321
        return self::OPEN_BRACKET.$left.' '.$operator.' '.$right.self::CLOSE_BRACKET;
322
    }
323
    /**
324
     * To format unary expression.
325
     *
326
     * @param string $operator The unary operator
327
     * @param string $child    The operand
328
     *
329
     * @return string
330
     */
331
    private function prepareUnaryExpression($operator, $child)
332
    {
333
        return $operator.self::OPEN_BRACKET.$child.self::CLOSE_BRACKET;
334
    }
335
}
336