Passed
Branch master (11b479)
by Mikael
03:34
created

RouteMatcher::checkPartMatchingType()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 6.2017

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 7
cts 11
cp 0.6364
rs 9.2408
c 0
b 0
f 0
cc 5
nc 5
nop 2
crap 6.2017
1
<?php
2
3
namespace Anax\Route;
4
5
use Anax\Commons\ContainerInjectableInterface;
6
use Anax\Route\Exception\ConfigurationException;
7
8
/**
9
 * Matching a incoming path to see it it matches a route.
10
 */
11
class RouteMatcher
12
{
13
    /**
14
     * @var string       $methodMatched the matched method.
15
     * @var null|array   $arguments     arguments for the callback, extracted
16
     *                                  from path
17
     */
18
    public $methodMatched;
19
    public $arguments = [];
20
21
22
23
    /**
24
     * Check if part of route is a argument and optionally match type
25
     * as a requirement {argument:type}.
26
     *
27
     * @param string $rulePart   the rule part to check.
28
     * @param string $queryPart  the query part to check.
29
     * @param array  &$args      add argument to args array if matched
30
     *
31
     * @return boolean
32
     */
33 4
    private function checkPartAsArgument($rulePart, $queryPart, &$args)
34
    {
35 4
        if (substr($rulePart, -1) == "}"
36 4
            && !is_null($queryPart)
37
        ) {
38 4
            $part = substr($rulePart, 1, -1);
39 4
            $pos = strpos($part, ":");
40 4
            $type = null;
41 4
            if ($pos !== false) {
42 1
                $type = substr($part, $pos + 1);
43 1
                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...
44 1
                    return false;
45
                }
46
            }
47 4
            $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...
48 4
            return true;
49
        }
50 3
        return false;
51
    }
52
53
54
55
    /**
56
     * Check if value is matching a certain type of values.
57
     *
58
     * @param string $value   the value to check.
59
     * @param array  $type    the expected type to check against.
60
     *
61
     * @return boolean
62
     */
63 1
    private function checkPartMatchingType($value, $type)
64
    {
65 1
        switch ($type) {
66
            case "digit":
67 1
                return ctype_digit($value);
68
                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...
69
70
            case "hex":
71 1
                return ctype_xdigit($value);
72
                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...
73
74
            case "alpha":
75 1
                return ctype_alpha($value);
76
                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...
77
78
            case "alphanum":
79 1
                return ctype_alnum($value);
80
                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...
81
82
            default:
83 1
                return false;
84
        }
85
    }
86
87
88
89
    /**
90
     * Check if value is matching a certain type and do type
91
     * conversion accordingly.
92
     *
93
     * @param string $value   the value to check.
94
     * @param array  $type    the expected type to check against.
95
     *
96
     * @return boolean
97
     */
98 4
    private function typeConvertArgument($value, $type)
99
    {
100 4
        switch ($type) {
101 3
            case "digit":
102 1
                return (int) $value;
103
                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...
104
105
            default:
106 4
                return $value;
107
        }
108
    }
109
110
111
112
    /**
113
     * Match part of rule and query.
114
     *
115
     * @param string $rulePart   the rule part to check.
116
     * @param string $queryPart  the query part to check.
117
     * @param array  &$args      add argument to args array if matched
118
     *
119
     * @return boolean
120
     */
121 58
    private function matchPart($rulePart, $queryPart, &$args)
122
    {
123 58
        $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...
124 58
        $first = isset($rulePart[0]) ? $rulePart[0] : '';
125 58
        switch ($first) {
126
            case '*':
127 2
                $match = true;
128 2
                break;
129
130
            case '{':
131 4
                $match = $this->checkPartAsArgument($rulePart, $queryPart, $args);
132 4
                break;
133
134
            default:
135 57
                $match = ($rulePart == $queryPart);
136 57
                break;
137
        }
138 58
        return $match;
139
    }
140
141
142
143
    /**
144
     * Check if the request method matches.
145
     *
146
     * @param string $method    as request method.
147
     * @param string $supported as request methods that are valid.
148
     *
149
     * @return boolean true if request method matches
150
     */
151 122
    private function matchRequestMethod(
152
        string $method = null,
153
        array $supported = null
154
    ) {
155 122
        if ($supported && !in_array($method, $supported)) {
156 42
            return false;
157
        }
158
159 122
        return true;
160
    }
161
162
163
164
    /**
165
     * Check if the route matches a query and request method.
166
     *
167
     * @param string $path            of the current route being matched.
168
     * @param string $query           to match against
169
     * @param array  $methodSupported as supported request method
170
     * @param string $method          as request method
171
     *
172
     * @return boolean true if query matches the route
173
     */
174 122
    public function match(
175
        string $path = null,
176
        string $query,
177
        array $methodSupported = null,
178
        string $method = null
179
    ) {
180 122
        $this->arguments = [];
181 122
        $this->methodMatched = null;
182
183 122
        if (!$this->matchRequestMethod($method, $methodSupported)) {
184 42
            return false;
185
        }
186
187
        // If any/default */** route, match anything
188 122
        if (is_null($path)
189 122
            || in_array($path, ["*", "**"])
190
        ) {
191 65
            $this->methodMatched = $method;
192 65
            return true;
193
        }
194
195
        // Check all parts to see if they matches
196 58
        $ruleParts  = explode('/', $path);
197 58
        $queryParts = explode('/', $query);
198 58
        $ruleCount = max(count($ruleParts), count($queryParts));
199 58
        $args = [];
200
201 58
        for ($i = 0; $i < $ruleCount; $i++) {
202 58
            $rulePart  = isset($ruleParts[$i])  ? $ruleParts[$i]  : null;
203 58
            $queryPart = isset($queryParts[$i]) ? $queryParts[$i] : null;
204
205 58
            if ($rulePart === "**") {
206 2
                break;
207
            }
208
209 58
            if (!$this->matchPart($rulePart, $queryPart, $args)) {
210 13
                return false;
211
            }
212
        }
213
214 57
        $this->arguments = $args;
215 57
        $this->methodMatched = $method;
216 57
        return true;
217
    }
218
}
219