Passed
Pull Request — 3.x (#208)
by
unknown
01:44
created

Map::get()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 3
dl 0
loc 5
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
/**
3
 *
4
 * This file is part of Aura for PHP.
5
 *
6
 * @license http://opensource.org/licenses/bsd-license.php BSD
7
 *
8
 */
9
namespace Aura\Router;
10
11
use ArrayIterator;
12
use IteratorAggregate;
13
14
/**
15
 *
16
 * A collection of route objects.
17
 *
18
 * @package Aura.Router
19
 *
20
 * @method Route accepts(string|array $accepts)
21
 *
22
 * @method Route allows(string|array $allows)
23
 *
24
 * @method Route attributes(array $attributes)
25
 *
26
 * @method Route auth(mixed $auth)
27
 *
28
 * @method Route defaults(array $defaults)
29
 *
30
 * @method Route extras(array $extras)
31
 *
32
 * @method Route failedRule(mixed $failedRule)
33
 *
34
 * @method Route handler(mixed $handler)
35
 *
36
 * @method Route host(mixed $host)
37
 *
38
 * @method Route isRoutable(bool $isRoutable = true)
39
 *
40
 * @method Route namePrefix(string $namePrefix)
41
 *
42
 * @method Route path(string $path)
43
 *
44
 * @method Route pathPrefix(string $pathPrefix)
45
 *
46
 * @method Route secure(bool|null $secure = true)
47
 *
48
 * @method Route special(callable|null $host)
49
 *
50
 * @method Route tokens(array $tokens)
51
 *
52
 * @method Route wildcard(string $wildcard)
53
 *
54
 */
55
class Map implements IteratorAggregate
56
{
57
    /**
58
     *
59
     * An array of route objects.
60
     *
61
     * @var Route[]
62
     *
63
     */
64
    protected $routes = [];
65
66
    /**
67
     *
68
     * A prototype Route.
69
     *
70
     * @var Route
71
     *
72
     */
73
    protected $protoRoute;
74
75
    /**
76
     *
77
     * Constructor.
78
     *
79
     * @param Route $protoRoute A prototype Route.
80
     *
81
     */
82 24
    public function __construct(Route $protoRoute)
83
    {
84 24
        $this->protoRoute = $protoRoute;
85 24
    }
86
87
    /**
88
     *
89
     * Proxy unknown method calls to the proto-route.
90
     *
91
     * @param string $method The method name.
92
     *
93
     * @param array $params The method params.
94
     *
95
     * @return $this
96
     *
97
     */
98 3
    public function __call($method, $params)
99
    {
100 3
        call_user_func_array([$this->protoRoute, $method], $params);
101 3
        return $this;
102
    }
103
104
    /**
105
     *
106
     * IteratorAggregate: returns the iterator object.
107
     *
108
     * @return ArrayIterator
109
     *
110
     */
111 5
    #[\ReturnTypeWillChange]
112
    public function getIterator()
113 5
    {
114
        return new ArrayIterator($this->routes);
115
    }
116
117
    /**
118
     *
119
     * Sets the array of route objects to use.
120
     *
121
     * @param Route[] $routes Use this array of routes.
122
     *
123
     * @return void
124
     *
125
     * @see getRoutes()
126
     *
127 1
     */
128
    public function setRoutes(array $routes)
129 1
    {
130 1
        $this->routes = $routes;
131
    }
132
133
    /**
134
     *
135
     * Gets the route collection.
136
     *
137
     * @return Route[]
138
     *
139
     * @see setRoutes()
140
     *
141 3
     */
142
    public function getRoutes()
143 3
    {
144
        return $this->routes;
145
    }
146
147
    /**
148
     *
149
     * Adds a pre-built route to the collection.
150
     *
151
     * @param Route $route The pre-built route.
152
     *
153
     * @return void
154
     *
155
     * @throws Exception\RouteAlreadyExists when the route name is already
156
     * mapped.
157
     *
158 23
     */
159
    public function addRoute(Route $route)
160 23
    {
161
        $name = $route->name;
162 23
163 1
        if (! $name) {
164 1
            $this->routes[] = $route;
165
            return;
166
        }
167 22
168 1
        if (isset($this->routes[$name])) {
169
            throw new Exception\RouteAlreadyExists($name);
170
        }
171 22
172 22
        $this->routes[$name] = $route;
173
    }
174
175
    /**
176
     *
177
     * Gets a route by name.
178
     *
179
     * @param string $name The route name.
180
     *
181
     * @throws Exception\RouteNotFound
182
     *
183
     * @return Route
184
     *
185 14
     */
186
    public function getRoute($name)
187 14
    {
188 1
        if (! isset($this->routes[$name])) {
189
            throw new Exception\RouteNotFound($name);
190
        }
191 13
192
        return $this->routes[$name];
193
    }
194
195
    /**
196
     *
197
     * Adds a generic route.
198
     *
199
     * @param string $name The route name.
200
     *
201
     * @param string $path The route path.
202
     *
203
     * @param mixed $handler The route leads to this handler.
204
     *
205
     * @throws Exception\ImmutableProperty
206
     *
207 23
     * @throws Exception\RouteAlreadyExists
208
     *
209 23
     * @return Route The newly-added route object.
210 23
     *
211 23
     */
212 23
    public function route($name, $path, $handler = null)
213 23
    {
214 23
        $route = clone $this->protoRoute;
215
        $route->name($name);
216
        $route->path($path);
217
        $route->handler($handler);
218
        $this->addRoute($route);
219
        return $route;
220
    }
221
222
    /**
223
     *
224
     * Adds a GET route.
225
     *
226
     * @param string $name The route name.
227
     *
228
     * @param string $path The route path.
229
     *
230 1
     * @param mixed $handler The route leads to this handler.
231
     *
232 1
     * @throws Exception\ImmutableProperty
233 1
     *
234 1
     * @throws Exception\RouteAlreadyExists
235
     *
236
     * @return Route The newly-added route object.
237
     *
238
     */
239
    public function get($name, $path, $handler = null)
240
    {
241
        $route = $this->route($name, $path, $handler);
242
        $route->allows('GET');
243
        return $route;
244
    }
245
246
    /**
247
     *
248
     * Adds a DELETE route.
249
     *
250 2
     * @param string $name The route name.
251
     *
252 2
     * @param string $path The route path.
253 2
     *
254 2
     * @param mixed $handler The route leads to this handler.
255
     *
256
     * @throws Exception\ImmutableProperty
257
     *
258
     * @throws Exception\RouteAlreadyExists
259
     *
260
     * @return Route The newly-added route object.
261
     *
262
     */
263
    public function delete($name, $path, $handler = null)
264
    {
265
        $route = $this->route($name, $path, $handler);
266
        $route->allows('DELETE');
267
        return $route;
268
    }
269
270 1
    /**
271
     *
272 1
     * Adds a HEAD route.
273 1
     *
274 1
     * @param string $name The route name.
275
     *
276
     * @param string $path The route path.
277
     *
278
     * @param mixed $handler The route leads to this handler.
279
     *
280
     * @throws Exception\ImmutableProperty
281
     *
282
     * @throws Exception\RouteAlreadyExists
283
     *
284
     * @return Route The newly-added route object.
285
     *
286
     */
287
    public function head($name, $path, $handler = null)
288
    {
289
        $route = $this->route($name, $path, $handler);
290 1
        $route->allows('HEAD');
291
        return $route;
292 1
    }
293 1
294 1
    /**
295
     *
296
     * Adds an OPTIONS route.
297
     *
298
     * @param string $name The route name.
299
     *
300
     * @param string $path The route path.
301
     *
302
     * @param mixed $handler The route leads to this handler.
303
     *
304
     * @throws Exception\ImmutableProperty
305
     *
306
     * @throws Exception\RouteAlreadyExists
307
     *
308
     * @return Route The newly-added route object.
309
     *
310 1
     */
311
    public function options($name, $path, $handler = null)
312 1
    {
313 1
        $route = $this->route($name, $path, $handler);
314 1
        $route->allows('OPTIONS');
315
        return $route;
316
    }
317
318
    /**
319
     *
320
     * Adds a PATCH route.
321
     *
322
     * @param string $name The route name.
323
     *
324
     * @param string $path The route path.
325
     *
326
     * @param mixed $handler The route leads to this handler.
327
     *
328
     * @throws Exception\ImmutableProperty
329
     *
330 3
     * @throws Exception\RouteAlreadyExists
331
     *
332 3
     * @return Route The newly-added route object.
333 3
     *
334 3
     */
335
    public function patch($name, $path, $handler = null)
336
    {
337
        $route = $this->route($name, $path, $handler);
338
        $route->allows('PATCH');
339
        return $route;
340
    }
341
342
    /**
343
     *
344
     * Adds a POST route.
345
     *
346
     * @param string $name The route name.
347
     *
348
     * @param string $path The route path.
349
     *
350 1
     * @param mixed $handler The route leads to this handler.
351
     *
352 1
     * @throws Exception\ImmutableProperty
353 1
     *
354 1
     * @throws Exception\RouteAlreadyExists
355
     *
356
     * @return Route The newly-added route object.
357
     *
358
     */
359
    public function post($name, $path, $handler = null)
360
    {
361
        $route = $this->route($name, $path, $handler);
362
        $route->allows('POST');
363
        return $route;
364
    }
365
366
    /**
367
     *
368
     * Adds a PUT route.
369
     *
370
     * @param string $name The route name.
371
     *
372
     * @param string $path The route path.
373 4
     *
374
     * @param mixed $handler The route leads to this handler.
375
     *
376 4
     * @throws Exception\ImmutableProperty
377
     *
378
     * @throws Exception\RouteAlreadyExists
379 4
     *
380 4
     * @return Route The newly-added route object.
381 4
     *
382 4
     */
383
    public function put($name, $path, $handler = null)
384
    {
385 4
        $route = $this->route($name, $path, $handler);
386 4
        $route->allows('PUT');
387 4
        return $route;
388
    }
389
390
    /**
391
     *
392
     * Attaches routes to a specific path prefix, and prefixes the attached
393
     * route names.
394
     *
395
     * @param string $namePrefix The prefix for all route names being attached.
396
     *
397
     * @param string $pathPrefix The prefix for all route paths being attached.
398
     *
399
     * @param callable $callable A callable that uses the Map to add new
400
     * routes. Its signature is `function (\Aura\Router\Map $map)`; $this
401
     * Map instance will be passed to the callable.
402
     *
403
     * @throws Exception\ImmutableProperty
404
     *
405
     * @return void
406
     *
407
     */
408
    public function attach($namePrefix, $pathPrefix, callable $callable)
409
    {
410
        // retain current prototype
411
        $old = $this->protoRoute;
412
413
        // clone a new prototype, update prefixes, and retain it
414
        $new = clone $old;
415
        $new->namePrefix($old->namePrefix . $namePrefix);
416
        $new->pathPrefix($old->pathPrefix . $pathPrefix);
417
        $this->protoRoute = $new;
418
419
        // run the callable and restore the old prototype
420
        $callable($this);
421
        $this->protoRoute = $old;
422
    }
423
424
    /**
425
     * Convert all routes into a node tree array structure that contains all possible routes per route segment
426
     * This will reduce the amount of possible routes to check
427
     *
428
     * @return array<string, Route|array<string, mixed>>
429
     */
430
    public function getAsTreeRouteNode()
431
    {
432
        $treeRoutes = [];
433
        foreach ($this->routes as $route) {
434
            if (! $route->isRoutable) {
435
                continue;
436
            }
437
438
            // replace all parameters with {}
439
            // This regexp will also work with "{controller:[a-zA-Z][a-zA-Z0-9_-]{1,}}"
440
            $routePath = preg_replace('~{(?:[^{}]*|(?R))*}~', '{}', $route->path);
441
            $node = &$treeRoutes;
442
            foreach (explode('/', trim($routePath, '/')) as $segment) {
443
                if (strpos($segment, '{') === 0 || strpos($segment, ':') === 0) {
444
                    for ($i = 0; $i <= substr_count($segment, ','); $i++) {
445
                        $node = &$node['{}'];
446
                        $node[spl_object_hash($route)] = $route;
447
                    }
448
                    continue;
449
                }
450
                $node = &$node[$segment];
451
            }
452
453
            $node[spl_object_hash($route)] = $route;
454
            unset($node);
455
        }
456
457
        return $treeRoutes;
458
    }
459
}
460