Test Failed
Push — master ( 29bae6...f97d4a )
by Mikael
01:49
created

Route::getName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 1
cts 1
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
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 $di container with services
244
     *
245
     * @return mixed
246
     */
247 26
    public function handle($di = 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/di injected container
254
        if ($di
0 ignored issues
show
Bug Best Practice introduced by
The expression $di 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
            && $di->has($this->action[0])
0 ignored issues
show
Bug introduced by
The method has cannot be called on $di (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
260
        ) {
261
            $service = $di->get($this->action[0]);
0 ignored issues
show
Bug introduced by
The method get cannot be called on $di (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
262
            if (is_callable([$service, $this->action[1]])) {
263
                return call_user_func(
264
                    [$service, $this->action[1]],
265
                    ...$this->arguments
266
                );
267
            }
268
        }
269
    }
270
271
272
273
    /**
274
     * Set the name of the route.
275
     *
276
     * @param string $name set a name for the route
277
     *
278
     * @return $this
279
     */
280
    public function setName($name)
281
    {
282
        $this->name = $name;
283
        return $this;
284
    }
285
286
287
288
    /**
289
     * Get the name of the route.
290
     *
291 24
     * @return string as the route name.
292
     */
293 24
    public function getName()
294
    {
295
        return $this->name;
296
    }
297
298
299
300
    /**
301
     * Get the rule for the route.
302
     *
303
     * @return string
304
     */
305
    public function getRule()
306
    {
307
        return $this->rule;
308
    }
309
310
311
312
    /**
313
     * Get the request method for the route.
314
     *
315
     * @return string representing the request method supported
316
     */
317
    public function getRequestMethod()
318
    {
319
        if (is_array($this->method)) {
320
            return implode("|", $this->method);
321
        }
322
323
        return $this->method;
324
    }
325
}
326