Passed
Branch dev (473855)
by Alex
02:25
created

Collector::group()   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 1
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 39
    public function set($method, $pattern, $action)
107
    {
108 39
        $method   = $this->parseMethod($method);
109 38
        $patterns = $this->parsePattern($pattern);
110 35
        $group    = new Group;
111
112 35
        foreach ($patterns as $pattern)
113
        {
114 35
            $route = new Route($this, $method, $pattern, $action);
115 35
            $group->setRoute($route);
116
117 35
            if (strpos($pattern, "{") !== false) {
118 24
                   $index = $this->getDynamicIndex($method, $pattern);
119 24
                   $this->dynamics[$index][$pattern] = $route;
120 35
            } else $this->statics[$method][$pattern] = $route;
121 35
        }
122
123 35
        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 2
    public function group(array $routes)
187
    {
188 2
        $group = new Group;
189 2
        foreach ($routes as $route)
190 2
            $group->set($route);
191 2
        return $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 39
    protected function parseMethod($method)
217
    {
218 39
        $method = strtolower($method);
219
220 39
        if (strpos(self::HTTP_METHODS, $method) === false) {
221 1
            throw new MethodNotSupportedException($method);
222
        }
223
224 38
        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 38
    protected function parsePattern($pattern)
235
    {
236 38
        $withoutClosing = rtrim($pattern, "]");
237 38
        $closingNumber  = strlen($pattern) - strlen($withoutClosing);
238
239 38
        $segments = preg_split("~" . self::DYNAMIC_REGEX . "(*SKIP)(*F)|\[~x", $withoutClosing);
240 38
        $this->parseSegments($segments, $closingNumber, $withoutClosing);
241
242 36
        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 38
    protected function parseSegments(array $segments, $closingNumber, $withoutClosing)
256
    {
257 38
        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 36
    }
263
264
    /**
265
     * @param string[] $segments
266
     *
267
     * @throws BadRouteException
268
     * @return array
269
     */
270
271 36
    protected function buildSegments(array $segments)
272
    {
273 36
        $pattern  = "";
274 36
        $patterns = [];
275 36
        $wildcardTokens = array_keys($this->wildcards);
276 36
        $wildcardRegex  = $this->wildcards;
277
278 36
        foreach ($segments as $n => $segment) {
279 36
            if ($segment === "" && $n !== 0) {
280 1
                throw new BadRouteException(BadRouteException::EMPTY_OPTIONAL_PARTS);
281
            }
282
283 36
            $patterns[] = $pattern .= str_replace($wildcardTokens, $wildcardRegex, $segment);
284 36
        }
285
286 35
        return $patterns;
287
    }
288
289
    /**
290
     * @param string $method
291
     * @param string $pattern
292
     *
293
     * @return Route|false
294
     */
295
296 34
    public function findStaticRoute($method, $pattern)
297
    {
298 34
        $method = strtolower($method);
299 34
        if (isset($this->statics[$method]) && isset($this->statics[$method][$pattern]))
300 34
            return $this->statics[$method][$pattern];
301 28
        return false;
302
    }
303
304
    /**
305
     * @param string $method
306
     * @param string $pattern
307
     *
308
     * @return array|false
309
     */
310
311 28
    public function findDynamicRoutes($method, $pattern)
312
    {
313 28
        $index = $this->getDynamicIndex($method, $pattern);
314 28
        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 28
    protected function getDynamicIndex($method, $pattern)
325
    {
326 28
        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
    public function getWildcard($wildcard)
356
    {
357
        return isset($this->wildcards[":$wildcard"]) ? $this->wildcards[":$wildcard"] : 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