Passed
Push — master ( d28518...6e985b )
by Mikael
01:46
created

Route::getRequestMethod()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 8
ccs 0
cts 4
cp 0
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 6
1
<?php
2
3
namespace Anax\Route;
4
5
/**
6
 * A container for routes.
7
 *
8
 */
9
class Route
10
{
11
    /**
12
     * @var string       $name      a name for this route.
13
     * @var string|array $method    the method(s) to support
14
     * @var string       $rule      the path rule for this route
15
     * @var callable     $action    the callback to handle this route
16
     * @var null|array   $arguments arguments for the callback, extracted
17
     *                              from path
18
     */
19
    private $name;
20
    private $method;
21
    private $rule;
22
    private $action;
23
    private $arguments = [];
24
25
26
27
    /**
28
     * Set values for route.
29
     *
30
     * @param null|string       $rule   for this route
31
     * @param callable          $action callable to implement a controller for
32
     *                                  the route
33
     * @param null|string|array $method as request method to support
34
     *
35
     * @return $this
36
     */
37 37
    public function set($rule, $action = null, $method = null)
38
    {
39 37
        $this->rule = $rule;
40 37
        $this->action = $action;
41
42 37
        $this->method = $method;
43 37
        if (is_string($method)) {
44 4
            $this->method = array_map("trim", explode("|", $method));
45 4
        }
46 37
        if (is_array($this->method)) {
47 9
            $this->method = array_map("strtoupper", $this->method);
48 9
        }
49
50 37
        return $this;
51
    }
52
53
54
55
    /**
56
     * Check if part of route is a argument and optionally match type
57
     * as a requirement {argument:type}.
58
     *
59
     * @param string $rulePart   the rule part to check.
60
     * @param string $queryPart  the query part to check.
61
     * @param array  &$args      add argument to args array if matched
62
     *
63
     * @return boolean
64
     */
65 7
    private function checkPartAsArgument($rulePart, $queryPart, &$args)
66
    {
67 7
        if (substr($rulePart, -1) == "}"
68 7
            && !is_null($queryPart)
69 7
        ) {
70 7
            $part = substr($rulePart, 1, -1);
71 7
            $pos = strpos($part, ":");
72 7
            $type = null;
73 7
            if ($pos !== false) {
74 2
                $type = substr($part, $pos + 1);
75 2
                if (! $this->checkPartMatchingType($queryPart, $type)) {
0 ignored issues
show
Documentation introduced by
$type is of type string, but the function expects a array.

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...
Bug Best Practice introduced by
The expression $this->checkPartMatchingType($queryPart, $type) of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
76 2
                    return false;
77
                }
78 2
            }
79 7
            $args[] = $this->typeConvertArgument($queryPart, $type);
0 ignored issues
show
Documentation introduced by
$type is of type string|null, but the function expects a array.

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...
80 7
            return true;
81
        }
82 3
        return false;
83
    }
84
85
86
87
    /**
88
     * Check if value is matching a certain type of values.
89
     *
90
     * @param string $value   the value to check.
91
     * @param array  $type    the expected type to check against.
92
     *
93
     * @return boolean
94
     */
95 2
    private function checkPartMatchingType($value, $type)
96
    {
97
        switch ($type) {
98 2
            case "digit":
99 2
                return ctype_digit($value);
100
                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...
101
102 2
            case "hex":
103 1
                return ctype_xdigit($value);
104
                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...
105
106 2
            case "alpha":
107 2
                return ctype_alpha($value);
108
                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...
109
110 1
            case "alphanum":
111 1
                return ctype_alnum($value);
112
                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...
113
114
            default:
115
                return false;
116
        }
117
    }
118
119
120
121
    /**
122
     * Check if value is matching a certain type and do type
123
     * conversion accordingly.
124
     *
125
     * @param string $value   the value to check.
126
     * @param array  $type    the expected type to check against.
127
     *
128
     * @return boolean
129
     */
130 7
    private function typeConvertArgument($value, $type)
131
    {
132
        switch ($type) {
133 7
            case "digit":
134 2
                return (int) $value;
135
                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...
136
137 7
            default:
138 7
                return $value;
139 7
        }
140
    }
141
142
143
144
    /**
145
     * Match part of rule and query.
146
     *
147
     * @param string $rulePart   the rule part to check.
148
     * @param string $queryPart  the query part to check.
149
     * @param array  &$args      add argument to args array if matched
150
     *
151
     * @return boolean
152
     */
153 30
    private function matchPart($rulePart, $queryPart, &$args)
154
    {
155 30
        $match = false;
0 ignored issues
show
Unused Code introduced by
$match 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...
156 30
        $first = isset($rulePart[0]) ? $rulePart[0] : '';
157
        switch ($first) {
158 30
            case '*':
159 1
                $match = true;
160 1
                break;
161
162 30
            case '{':
163 7
                $match = $this->checkPartAsArgument($rulePart, $queryPart, $args);
164 7
                break;
165
166 29
            default:
167 29
                $match = ($rulePart == $queryPart);
168 29
                break;
169 29
        }
170 30
        return $match;
171
    }
172
173
174
175
    /**
176
     * Check if the request method matches.
177
     *
178
     * @param string $method as request method
179
     *
180
     * @return boolean true if request method matches
181
     */
182 36
    public function matchRequestMethod($method)
183
    {
184 36
        if ($method && is_array($this->method) && !in_array($method, $this->method)
185 36
            || (is_null($method) && !empty($this->method))
186 36
        ) {
187 8
            return false;
188
        }
189 36
        return true;
190
    }
191
192
193
194
    /**
195
     * Check if the route matches a query and request method.
196
     *
197
     * @param string $query  to match against
198
     * @param string $method as request method
199
     *
200
     * @return boolean true if query matches the route
201
     */
202 36
    public function match($query, $method = null)
203
    {
204 36
        if (!$this->matchRequestMethod($method)) {
205 8
            return false;
206
        }
207
208
        // If any/default */** route, match anything
209 36
        if (is_null($this->rule)
210 33
            || in_array($this->rule, ["*", "**"])
211 36
        ) {
212 6
            return true;
213
        }
214
215
        // Check all parts to see if they matches
216 30
        $ruleParts  = explode('/', $this->rule);
217 30
        $queryParts = explode('/', $query);
218 30
        $ruleCount = max(count($ruleParts), count($queryParts));
219 30
        $args = [];
220
221 30
        for ($i = 0; $i < $ruleCount; $i++) {
222 30
            $rulePart  = isset($ruleParts[$i])  ? $ruleParts[$i]  : null;
223 30
            $queryPart = isset($queryParts[$i]) ? $queryParts[$i] : null;
224
225 30
            if ($rulePart === "**") {
226 2
                break;
227
            }
228
229 30
            if (!$this->matchPart($rulePart, $queryPart, $args)) {
230 14
                return false;
231
            }
232 29
        }
233
234 29
        $this->arguments = $args;
235 29
        return true;
236
    }
237
238
239
240
    /**
241
     * Handle the action for the route.
242
     *
243
     * @param string $app container with services
244
     *
245
     * @return void
246
     */
247 26
    public function handle($app = null)
248
    {
249 26
        if (is_callable($this->action)) {
250 26
            return call_user_func($this->action, ...$this->arguments);
251
        }
252
253
        // Try to load service from app injected container
254
        if ($app
0 ignored issues
show
Bug Best Practice introduced by
The expression $app of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
255
            && is_array($this->action)
256
            && isset($this->action[0])
257
            && isset($this->action[1])
258
            && is_string($this->action[0])
259
            && is_object($app->{$this->action[0]})
260
            && is_callable([$app->{$this->action[0]}, $this->action[1]])
261
        ) {
262
            return call_user_func(
263
                [$app->{$this->action[0]}, $this->action[1]],
264
                ...$this->arguments
265
            );
266
        }
267
    }
268
269
270
271
    /**
272
     * Set the name of the route.
273
     *
274
     * @param string $name set a name for the route
275
     *
276
     * @return $this
277
     */
278
    public function setName($name)
279
    {
280
        $this->name = $name;
281
        return $this;
282
    }
283
284
285
286
    /**
287
     * Get the rule for the route.
288
     *
289
     * @return string
290
     */
291 24
    public function getRule()
292
    {
293 24
        return $this->rule;
294
    }
295
296
297
298
    /**
299
     * Get the request method for the route.
300
     *
301
     * @return string representing the request method supported
302
     */
303
    public function getRequestMethod()
304
    {
305
        if (is_array($this->method)) {
306
            return implode("|", $this->method);
307
        }
308
309
        return $this->method;
310
    }
311
}
312