Test Failed
Branch dev (494019)
by Alex
03:13
created

Collector::any()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
c 2
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 2
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
include __DIR__ . "/Route.php";
23
include __DIR__ . "/Group.php";
24
include __DIR__ . "/Collectors/ControllerCollectorTrait.php";
25
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 callable $action
99
     *
100
     * @throws BadRouteException 
101
     * @throws MethodNotSupportedException
102
     *
103
     * @return Group
104
     */
105
106
    public function set($method, $pattern, $action)
107
    {
108
        $method   = $this->parseMethod($method);
109
        $patterns = $this->parsePattern($pattern);
110
        $group    = [];
111
112
        foreach ($patterns as $pattern)
113
        {
114
            $route   = new Route($this, $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...
115
            $group[] = $route;
116
117
            if (strpos($pattern, "{") !== false) {
118
                   $index = $this->getDynamicIndex($method, $pattern);
119
                   $this->dynamics[$index][$pattern] = $route;
120
            } else $this->statics[$method][$pattern] = $route;
121
        }
122
123
        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
    public function match(array $methods, $pattern, $action)
143
    {
144
        $group = [];
145
        foreach ($methods as $method)
146
            $group[] = $this->set($method, $pattern, $action);
147
        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
    public function any($pattern, $action)
160
    {
161
        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
    public function except(array $methods, $pattern, $action)
175
    {
176
        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
    public function group(array $routes)
187
    {
188
        $group = [];
189
        foreach ($routes as $route)
190
            $group[] = $route;
191
        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
    public function forget($method, $pattern)
202
    {
203
        if (strpos($pattern, "{") === false) {
204
               unset($this->statics[$method][$pattern]);
205
        } else unset($this->dynamics[$this->getDynamicIndex($method, $pattern)][$pattern]);
206
    }
207
208
    /**
209
     * Determine if the http method is valid.
210
     *
211
     * @param string $method
212
     * @throws MethodNotSupportedException
213
     * @return string
214
     */
215
216
    protected function parseMethod($method)
217
    {
218
        $method = strtolower($method);
219
220
        if (strpos(self::HTTP_METHODS, $method) === false) {
221
            throw new MethodNotSupportedException($method);
222
        }
223
224
        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
    protected function parsePattern($pattern)
235
    {
236
        $withoutClosing = rtrim($pattern, "]");
237
        $closingNumber  = strlen($pattern) - strlen($withoutClosing);
238
239
        $segments = preg_split("~" . self::DYNAMIC_REGEX . "(*SKIP)(*F)|\[~x", $withoutClosing);
240
        $this->parseSegments($segments, $closingNumber, $withoutClosing);
241
242
        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
    protected function parseSegments(array $segments, $closingNumber, $withoutClosing)
256
    {
257
        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
    }
263
264
    /**
265
     * @param string[] $segments
266
     *
267
     * @throws BadRouteException
268
     * @return array
269
     */
270
271
    protected function buildSegments(array $segments)
272
    {
273
        $pattern  = "";
274
        $patterns = [];
275
        $wildcardTokens = [];
276
        $wildcardRegex  = [];
277
278
        foreach ($this->wildcards as $token => $regex) {
279
            $wildcardTokens[] = ":$token";
280
            $wildcardRegex [] = ":$regex";
281
        }
282
283
        foreach ($segments as $n => $segment) {
284
            if ($segment === "" && $n !== 0) {
285
                throw new BadRouteException(BadRouteException::EMPTY_OPTIONAL_PARTS);
286
            }
287
288
            $patterns[] = $pattern .= str_replace($wildcardTokens, $wildcardRegex, $segment);
289
        }
290
291
        return $patterns;
292
    }
293
294
    /**
295
     * @param string $method
296
     * @param string $pattern
297
     *
298
     * @return array|false
299
     */
300
301
    public function findStaticRoute($method, $pattern)
302
    {
303
        $method = strtolower($method);
304
        if (isset($this->statics[$method]) && isset($this->statics[$method][$pattern]))
305
            return $this->statics[$method][$pattern];
306
        return false;
307
    }
308
309
    /**
310
     * @param string $method
311
     * @param string $pattern
312
     *
313
     * @return array|false
314
     */
315
316
    public function findDynamicRoutes($method, $pattern)
317
    {
318
        $index = $this->getDynamicIndex($method, $pattern);
319
        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
    protected function getDynamicIndex($method, $pattern)
330
    {
331
        return crc32(strtolower($method)) + substr_count($pattern, "/");
332
    }
333
334
    /**
335
     * @return string[]
336
     */
337
338
    public function getWildcards()
339
    {
340
        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