Completed
Push — master ( 2ea7db...8b621e )
by Mikael
02:36
created

RouteMatcher::match()   C

Complexity

Conditions 13
Paths 29

Size

Total Lines 64

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 31
CRAP Score 13.0051

Importance

Changes 0
Metric Value
dl 0
loc 64
ccs 31
cts 32
cp 0.9688
rs 6.0787
c 0
b 0
f 0
cc 13
nc 29
nop 6
crap 13.0051

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 null|array   $arguments     arguments for the callback, extracted
15
     *                                  from path
16
     * @var string       $methodMatched the matched method.
17
     * @var string       $pathMatched   the matched path.
18
     */
19
    public $arguments = [];
20
    public $methodMatched;
21
    public $pathMatched;
22
23
24
25
    /**
26
     * Check if part of route is a argument and optionally match type
27
     * as a requirement {argument:type}.
28
     *
29
     * @param string $rulePart   the rule part to check.
30
     * @param string $queryPart  the query part to check.
31
     * @param array  &$args      add argument to args array if matched
32
     *
33
     * @return boolean
34
     */
35 4
    private function checkPartAsArgument($rulePart, $queryPart, &$args)
36
    {
37 4
        if (substr($rulePart, -1) == "}"
38 4
            && !is_null($queryPart)
39
        ) {
40 4
            $part = substr($rulePart, 1, -1);
41 4
            $pos = strpos($part, ":");
42 4
            $type = null;
43 4
            if ($pos !== false) {
44 1
                $type = substr($part, $pos + 1);
45 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...
46 1
                    return false;
47
                }
48
            }
49 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...
50 4
            return true;
51
        }
52 3
        return false;
53
    }
54
55
56
57
    /**
58
     * Check if value is matching a certain type of values.
59
     *
60
     * @param string $value   the value to check.
61
     * @param array  $type    the expected type to check against.
62
     *
63
     * @return boolean
64
     */
65 1
    private function checkPartMatchingType($value, $type)
66
    {
67 1
        switch ($type) {
68 1
            case "digit":
69 1
                return ctype_digit($value);
70
                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...
71
72 1
            case "hex":
73 1
                return ctype_xdigit($value);
74
                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...
75
76 1
            case "alpha":
77 1
                return ctype_alpha($value);
78
                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...
79
80 1
            case "alphanum":
81 1
                return ctype_alnum($value);
82
                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...
83
84
            default:
85 1
                return false;
86
        }
87
    }
88
89
90
91
    /**
92
     * Check if value is matching a certain type and do type
93
     * conversion accordingly.
94
     *
95
     * @param string $value   the value to check.
96
     * @param array  $type    the expected type to check against.
97
     *
98
     * @return boolean
99
     */
100 4
    private function typeConvertArgument($value, $type)
101
    {
102 4
        switch ($type) {
103 4
            case "digit":
104 1
                return (int) $value;
105
                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...
106
107
            default:
108 4
                return $value;
109
        }
110
    }
111
112
113
114
    /**
115
     * Match part of rule and query.
116
     *
117
     * @param string $rulePart   the rule part to check.
118
     * @param string $queryPart  the query part to check.
119
     * @param array  &$args      add argument to args array if matched
120
     *
121
     * @return boolean
122
     */
123 81
    private function matchPart($rulePart, $queryPart, &$args)
124
    {
125 81
        $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...
126 81
        $first = isset($rulePart[0]) ? $rulePart[0] : '';
127 81
        switch ($first) {
128 81
            case '*':
129 3
                $match = true;
130 3
                break;
131
132 81
            case '{':
133 4
                $match = $this->checkPartAsArgument($rulePart, $queryPart, $args);
134 4
                break;
135
136
            default:
137 80
                $match = ($rulePart == $queryPart);
138 80
                break;
139
        }
140 81
        return $match;
141
    }
142
143
144
145
    /**
146
     * Check if the request method matches.
147
     *
148
     * @param string $method    as request method.
149
     * @param string $supported as request methods that are valid.
150
     *
151
     * @return boolean true if request method matches
152
     */
153 152
    private function matchRequestMethod(
154
        string $method = null,
155
        array $supported = null
156
    ) {
157 152
        if ($supported && !in_array($method, $supported)) {
158 42
            return false;
159
        }
160
161 152
        return true;
162
    }
163
164
165
166
    /**
167
     * Check if the route matches a query and request method.
168
     *
169
     * @param string $mount           of the current route being matched.
170
     * @param string $relativePath    of the current route being matched.
171
     * @param string $absolutePath    of the current route being matched.
172
     * @param string $query           to match against
173
     * @param array  $methodSupported as supported request method
174
     * @param string $method          as request method
175
     *
176
     * @return boolean true if query matches the route
177
     */
178 152
    public function match(
179
        ?string $mount,
180
        ?string $relativePath,
181
        ?string $absolutePath,
182
        ?string $query,
183
        ?array $methodSupported,
184
        ?string $method
185
    ) {
186 152
        $this->arguments = [];
187 152
        $this->methodMatched = null;
188 152
        $this->pathMatched = null;
189
190 152
        if (!$this->matchRequestMethod($method, $methodSupported)) {
191 42
            return false;
192
        }
193
194
        // Is a null path  - mounted on empty, or mount path matches
195
        // initial query.
196 152
        $charsToMatch = strlen($mount);
197 152
        $matchedQueryToMountPath = strncmp($query, $mount, $charsToMatch) === 0 ? true : false;
198 152
        $nextChar = substr($query, $charsToMatch, 1);
199 152
        if (is_null($relativePath)
200
            && (
201
                empty($mount)
202
                || (
203 152
                    $matchedQueryToMountPath && ($nextChar === "/" || $nextChar === "")
204
                )
205
            )
206
        ) {
207 75
            $matchedChars = strlen($mount);
208 75
            $nextChar = substr($query, $matchedChars, 1);
0 ignored issues
show
Unused Code introduced by
$nextChar 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...
209
            //var_dump($nextChar);
210
            // echo "TRUE";
211
            //echo "\nMATCHED: $query '$nextChar' == $mount (" . strlen($mount) . ")";
212
213 75
            $this->methodMatched = $method;
214 75
            $this->pathMatched = $query;
215 75
            return true;
216
        }
217
218
        // Check all parts to see if they matches
219 81
        $ruleParts  = explode('/', $absolutePath);
220 81
        $queryParts = explode('/', $query);
221 81
        $ruleCount = max(count($ruleParts), count($queryParts));
222 81
        $args = [];
223
224 81
        for ($i = 0; $i < $ruleCount; $i++) {
225 81
            $rulePart  = isset($ruleParts[$i])  ? $ruleParts[$i]  : null;
226 81
            $queryPart = isset($queryParts[$i]) ? $queryParts[$i] : null;
227
228 81
            if ($rulePart === "**") {
229 2
                break;
230
            }
231
232 81
            if (!$this->matchPart($rulePart, $queryPart, $args)) {
233 28
                return false;
234
            }
235
        }
236
237 76
        $this->arguments = $args;
238 76
        $this->methodMatched = $method;
239 76
        $this->pathMatched = $query;
240 76
        return true;
241
    }
242
}
243