Passed
Branch dev (140aec)
by Alex
02:37
created

Collector::getWildcards()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 5
nc 2
nop 0
crap 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 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. ps. all keys and values must start with :
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 46
    public function set($method, $pattern, $action)
107
    {
108 46
        $method   = $this->parseMethod($method);
109 45
        $patterns = $this->parsePattern($pattern);
110 42
        $group    = new Group;
111
112 42
        foreach ($patterns as $pattern)
113
        {
114 42
            $route = new Route($this, $method, $pattern, $action);
115 42
            $group->setRoute($route);
116
117 42
            if (strpos($pattern, "{") !== false) {
118 26
                   $index = $this->getDynamicIndex($method, $pattern);
119 26
                   $this->dynamics[$index][$pattern] = $route;
120 42
            } else $this->statics[$method][$pattern] = $route;
121 42
        }
122
123 42
        return $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 string|array|\Closure $action
138
     *
139
     * @return Group
140
     */
141
142 3
    public function match(array $methods, $pattern, $action)
143
    {
144 3
        $group = new Group;
145 3
        foreach ($methods as $method)
146 3
            $group->set($this->set($method, $pattern, $action));
147 3
        return $group;
148
    }
149
150
    /**
151
     * Insert a route into every http method supported.
152
     *
153
     * @param string $pattern
154
     * @param string|array|\Closure $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 string|array|\Closure $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 9
    public function group(array $routes)
187
    {
188 9
        $group = new Group;
189 9
        foreach ($routes as $route)
190 9
            $group->set($route);
191 9
        return $group;
192
    }
193
194
    /**
195
     * Remove a route from collector.
196
     *
197
     * @param string $method
198
     * @param string $pattern
199
     */
200
201 6
    public function forget($method, $pattern)
202
    {
203 6
        if (strpos($pattern, "{") === false) {
204 6
               unset($this->statics[$method][$pattern]);
205 6
        } else unset($this->dynamics[$this->getDynamicIndex($method, $pattern)][$pattern]);
206 6
    }
207
208
    /**
209
     * Determine if the http method is valid.
210
     *
211
     * @param string $method
212
     * @throws MethodNotSupportedException
213
     * @return string
214
     */
215
216 46
    protected function parseMethod($method)
217
    {
218 46
        $method = strtolower($method);
219
220 46
        if (strpos(self::HTTP_METHODS, $method) === false) {
221 1
            throw new MethodNotSupportedException($method);
222
        }
223
224 45
        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 45
    protected function parsePattern($pattern)
235
    {
236 45
        $withoutClosing = rtrim($pattern, "]");
237 45
        $closingNumber  = strlen($pattern) - strlen($withoutClosing);
238
239 45
        $segments = preg_split("~" . self::DYNAMIC_REGEX . "(*SKIP)(*F)|\[~x", $withoutClosing);
240 45
        $this->parseSegments($segments, $closingNumber, $withoutClosing);
241
242 43
        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 45
    protected function parseSegments(array $segments, $closingNumber, $withoutClosing)
256
    {
257 45
        if ($closingNumber !== count($segments) - 1) {
258 2
            if (preg_match("~" . self::DYNAMIC_REGEX . "(*SKIP)(*F)|\]~x", $withoutClosing)) {
259 1
                   throw new BadRouteException(BadRouteException::OPTIONAL_SEGMENTS_ON_MIDDLE);
260 1
            } else throw new BadRouteException(BadRouteException::UNCLOSED_OPTIONAL_SEGMENTS);
261
        }
262 43
    }
263
264
    /**
265
     * @param string[] $segments
266
     *
267
     * @throws BadRouteException
268
     * @return array
269
     */
270
271 43
    protected function buildSegments(array $segments)
272
    {
273 43
        $pattern  = "";
274 43
        $patterns = [];
275 43
        $wildcardTokens = array_keys($this->wildcards);
276 43
        $wildcardRegex  = $this->wildcards;
277
278 43
        foreach ($segments as $n => $segment) {
279 43
            if ($segment === "" && $n !== 0) {
280 1
                throw new BadRouteException(BadRouteException::EMPTY_OPTIONAL_PARTS);
281
            }
282
283 43
            $patterns[] = $pattern .= str_replace($wildcardTokens, $wildcardRegex, $segment);
284 43
        }
285
286 42
        return $patterns;
287
    }
288
289
    /**
290
     * @param string $method
291
     * @param string $pattern
292
     *
293
     * @return Route|false
294
     */
295
296 41
    public function findStaticRoute($method, $pattern)
297
    {
298 41
        $method = strtolower($method);
299 41
        if (isset($this->statics[$method]) && isset($this->statics[$method][$pattern]))
300 41
            return $this->statics[$method][$pattern];
301 30
        return false;
302
    }
303
304
    /**
305
     * @param string $method
306
     * @param string $pattern
307
     *
308
     * @return array|false
309
     */
310
311 30
    public function findDynamicRoutes($method, $pattern)
312
    {
313 30
        $index = $this->getDynamicIndex($method, $pattern);
314 30
        return isset($this->dynamics[$index]) ? $this->dynamics[$index] : false;
315
    }
316
317
    /**
318
     * @param string $method
319
     * @param string $pattern
320
     *
321
     * @return int
322
     */
323
324 30
    protected function getDynamicIndex($method, $pattern)
325
    {
326 30
        return crc32(strtolower($method)) + substr_count($pattern, "/");
327
    }
328
329
    /**
330
     * @return string[]
331
     */
332
333 7
    public function getWildcards()
334
    {
335 7
        $wildcards = [];
336 7
        foreach ($this->wildcards as $token => $regex)
337 7
            $wildcards[substr($token, 1)] = substr($regex, 1);
338 7
        return $wildcards;
339
    }
340
341
    /**
342
     * @return string[]
343
     */
344
345 1
    public function getWildcardTokens()
346
    {
347 1
        return $this->wildcards;
348
    }
349
350
    /**
351
     * @param string $wildcard
352
     * @return string|null
353
     */
354
355 1
    public function getWildcard($wildcard)
356
    {
357 1
        return isset($this->wildcards[":$wildcard"]) ? substr($this->wildcards[":$wildcard"], 1) : null;
358
    }
359
360
    /**
361
     * @param string $wildcard
362
     * @param string $pattern
363
     */
364
365 1
    public function setWildcard($wildcard, $pattern)
366
    {
367 1
        $this->wildcards[":$wildcard"] = ":$pattern";
368 1
    }
369
    
370
}
371