Passed
Branch dev (8b2306)
by Alex
02:48
created

Collector::parseMethod()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
ccs 4
cts 5
cp 0.8
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
crap 2.032
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 33 and the first side effect is on line 22.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
/**
4
 * Codeburner Framework.
5
 *
6
 * @author Alex Rohleder <[email protected]>
7
 * @copyright 2016 Alex Rohleder
8
 * @license http://opensource.org/licenses/MIT
9
 */
10
11
namespace Codeburner\Router;
12
13
use Codeburner\Router\Exceptions\BadRouteException;
14
use Codeburner\Router\Exceptions\MethodNotSupportedException;
15
16
/**
17
 * Explicit Avoiding autoload for classes and traits
18
 * that are aways needed. Don't need an condition of class exists
19
 * because the routes will not be used until the collector is used.
20
 */
21
22 1
include __DIR__ . "/Route.php";
23 1
include __DIR__ . "/Group.php";
24 1
include __DIR__ . "/Collectors/ControllerCollectorTrait.php";
25 1
include __DIR__ . "/Collectors/ResourceCollectorTrait.php";
26
27
/**
28
 * The Collector class hold, parse and build routes.
29
 *
30
 * @author Alex Rohleder <[email protected]>
31
 */
32
33
class Collector
34
{
35
36
    use Collectors\ControllerCollectorTrait;
37
    use Collectors\ResourceCollectorTrait;
38
39
    /**
40
     * These regex define the structure of a dynamic segment in a pattern.
41
     *
42
     * @var string
43
     */
44
45
    const DYNAMIC_REGEX = "{\s*(\w*)\s*(?::\s*([^{}]*(?:{(?-1)}*)*))?\s*}";
46
47
48
    /**
49
     * All the supported http methods separated by spaces.
50
     *
51
     * @var string
52
     */
53
54
    const HTTP_METHODS = "get post put patch delete";
55
56
    /**
57
     * The static routes are simple stored in a multidimensional array, the first
58
     * dimension is indexed by an http method and hold an array indexed with the patterns
59
     * and holding the route. ex. [METHOD => [PATTERN => ROUTE]]
60
     *
61
     * @var array
62
     */
63
64
    protected $statics  = [];
65
66
    /**
67
     * The dynamic routes have parameters and are stored in a hashtable that every cell have
68
     * an array with route patterns as indexes and routes as values. ex. [INDEX => [PATTERN => ROUTE]]
69
     *
70
     * @var array
71
     */
72
73
    protected $dynamics = [];
74
75
    /**
76
     * Some regex wildcards for easily definition of dynamic routes.
77
     *
78
     * @var array
79
     */
80
81
    protected $wildcards = [
82
        "uid"     => "uid-[a-zA-Z0-9]",
83
        "slug"    => "[a-z0-9-]",
84
        "string"  => "\w",
85
        "int"     => "\d",
86
        "integer" => "\d",
87
        "float"   => "[-+]?\d*?[.]?\d",
88
        "double"  => "[-+]?\d*?[.]?\d",
89
        "hex"     => "0[xX][0-9a-fA-F]",
90
        "octal"   => "0[1-7][0-7]",
91
        "bool"    => "1|0|true|false|yes|no",
92
        "boolean" => "1|0|true|false|yes|no",
93
    ];
94
95
    /**
96
     * @param string $method
97
     * @param string $pattern
98
     * @param string|array|\Closure $action
99
     *
100
     * @throws BadRouteException 
101
     * @throws MethodNotSupportedException
102
     *
103
     * @return Group
104
     */
105
106 28
    public function set($method, $pattern, $action)
107
    {
108 28
        $method   = $this->parseMethod($method);
109 28
        $patterns = $this->parsePattern($pattern);
110 28
        $group    = [];
111
112 28
        foreach ($patterns as $pattern)
113
        {
114 28
            $route   = new Route($this, $method, $pattern, $action);
115 28
            $group[] = $route;
116
117 28
            if (strpos($pattern, "{") !== false) {
118 19
                   $index = $this->getDynamicIndex($method, $pattern);
119 19
                   $this->dynamics[$index][$pattern] = $route;
120 28
            } else $this->statics[$method][$pattern] = $route;
121 28
        }
122
123 28
        return new Group($group);
124
    }
125
126
    public function get   ($pattern, $action) { return $this->set("get"   , $pattern, $action); }
127
    public function post  ($pattern, $action) { return $this->set("post"  , $pattern, $action); }
128
    public function put   ($pattern, $action) { return $this->set("put"   , $pattern, $action); }
129
    public function patch ($pattern, $action) { return $this->set("patch" , $pattern, $action); }
130
    public function delete($pattern, $action) { return $this->set("delete", $pattern, $action); }
131
132
    /**
133
     * Insert a route into several http methods.
134
     *
135
     * @param string[] $methods
136
     * @param string   $pattern
137
     * @param Callable $action
138
     *
139
     * @return Group
140
     */
141
142 3
    public function match(array $methods, $pattern, $action)
143
    {
144 3
        $group = [];
145 3
        foreach ($methods as $method)
146 3
            $group[] = $this->set($method, $pattern, $action);
0 ignored issues
show
Documentation introduced by
$action is of type callable, but the function expects a string|array|object<Closure>.

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...
147 3
        return new Group($group);
148
    }
149
150
    /**
151
     * Insert a route into every http method supported.
152
     *
153
     * @param string   $pattern
154
     * @param Callable $action
155
     *
156
     * @return Group
157
     */
158
159 1
    public function any($pattern, $action)
160
    {
161 1
        return $this->match(explode(" ", self::HTTP_METHODS), $pattern, $action);
162
    }
163
164
    /**
165
     * Insert a route into every http method supported but the given ones.
166
     *
167
     * @param string[] $methods
168
     * @param string   $pattern
169
     * @param Callable $action
170
     *
171
     * @return Group
172
     */
173
174 1
    public function except(array $methods, $pattern, $action)
175
    {
176 1
        return $this->match(array_diff(explode(" ", self::HTTP_METHODS), $methods), $pattern, $action);
177
    }
178
179
    /**
180
     * Group all given routes.
181
     *
182
     * @param Route[] $routes
183
     * @return Group
184
     */
185
186 1
    public function group(array $routes)
187
    {
188 1
        $group = [];
189 1
        foreach ($routes as $route)
190 1
            $group[] = $route;
191 1
        return new Group($group);
192
    }
193
194
    /**
195
     * Remove a route from collector.
196
     *
197
     * @param string $method
198
     * @param string $pattern
199
     */
200
201 5
    public function forget($method, $pattern)
202
    {
203 5
        if (strpos($pattern, "{") === false) {
204 5
               unset($this->statics[$method][$pattern]);
205 5
        } else unset($this->dynamics[$this->getDynamicIndex($method, $pattern)][$pattern]);
206 5
    }
207
208
    /**
209
     * Determine if the http method is valid.
210
     *
211
     * @param string $method
212
     * @throws MethodNotSupportedException
213
     * @return string
214
     */
215
216 28
    protected function parseMethod($method)
217
    {
218 28
        $method = strtolower($method);
219
220 28
        if (strpos(self::HTTP_METHODS, $method) === false) {
221
            throw new MethodNotSupportedException($method);
222
        }
223
224 28
        return $method;
225
    }
226
227
    /**
228
     * Separate routes pattern with optional parts into n new patterns.
229
     *
230
     * @param  string $pattern
231
     * @return array
232
     */
233
234 28
    protected function parsePattern($pattern)
235
    {
236 28
        $withoutClosing = rtrim($pattern, "]");
237 28
        $closingNumber  = strlen($pattern) - strlen($withoutClosing);
238
239 28
        $segments = preg_split("~" . self::DYNAMIC_REGEX . "(*SKIP)(*F)|\[~x", $withoutClosing);
240 28
        $this->parseSegments($segments, $closingNumber, $withoutClosing);
241
242 28
        return $this->buildSegments($segments);
243
    }
244
245
    /**
246
     * Parse all the possible patterns seeking for an incorrect or incompatible pattern.
247
     *
248
     * @param string[] $segments       Segments are all the possible patterns made on top of a pattern with optional segments.
249
     * @param int      $closingNumber  The count of optional segments.
250
     * @param string   $withoutClosing The pattern without the closing token of an optional segment. aka: ]
251
     *
252
     * @throws BadRouteException
253
     */
254
255 28
    protected function parseSegments(array $segments, $closingNumber, $withoutClosing)
256
    {
257 28
        if ($closingNumber !== count($segments) - 1) {
258
            if (preg_match("~" . self::DYNAMIC_REGEX . "(*SKIP)(*F)|\]~x", $withoutClosing)) {
259
                   throw new BadRouteException(BadRouteException::OPTIONAL_SEGMENTS_ON_MIDDLE);
260
            } else throw new BadRouteException(BadRouteException::UNCLOSED_OPTIONAL_SEGMENTS);
261
        }
262 28
    }
263
264
    /**
265
     * @param string[] $segments
266
     *
267
     * @throws BadRouteException
268
     * @return array
269
     */
270
271 28
    protected function buildSegments(array $segments)
272
    {
273 28
        $pattern  = "";
274 28
        $patterns = [];
275 28
        $wildcardTokens = [];
276 28
        $wildcardRegex  = [];
277
278 28
        foreach ($this->wildcards as $token => $regex) {
279 28
            $wildcardTokens[] = ":$token";
280 28
            $wildcardRegex [] = ":$regex";
281 28
        }
282
283 28
        foreach ($segments as $n => $segment) {
284 28
            if ($segment === "" && $n !== 0) {
285
                throw new BadRouteException(BadRouteException::EMPTY_OPTIONAL_PARTS);
286
            }
287
288 28
            $patterns[] = $pattern .= str_replace($wildcardTokens, $wildcardRegex, $segment);
289 28
        }
290
291 28
        return $patterns;
292
    }
293
294
    /**
295
     * @param string $method
296
     * @param string $pattern
297
     *
298
     * @return Route|false
299
     */
300
301 27
    public function findStaticRoute($method, $pattern)
302
    {
303 27
        $method = strtolower($method);
304 27
        if (isset($this->statics[$method]) && isset($this->statics[$method][$pattern]))
305 27
            return $this->statics[$method][$pattern];
306 22
        return false;
307
    }
308
309
    /**
310
     * @param string $method
311
     * @param string $pattern
312
     *
313
     * @return array|false
314
     */
315
316 22
    public function findDynamicRoutes($method, $pattern)
317
    {
318 22
        $index = $this->getDynamicIndex($method, $pattern);
319 22
        return isset($this->dynamics[$index]) ? $this->dynamics[$index] : false;
320
    }
321
322
    /**
323
     * @param string $method
324
     * @param string $pattern
325
     *
326
     * @return int
327
     */
328
329 22
    protected function getDynamicIndex($method, $pattern)
330
    {
331 22
        return crc32(strtolower($method)) + substr_count($pattern, "/");
332
    }
333
334
    /**
335
     * @return string[]
336
     */
337
338 7
    public function getWildcards()
339
    {
340 7
        return $this->wildcards;
341
    }
342
343
    /**
344
     * @param string $pattern
345
     * @param string $wildcard
346
     */
347
348
    public function setWildcard($pattern, $wildcard)
349
    {
350
        $this->wildcards[$pattern] = $wildcard;
351
    }
352
    
353
}
354