Passed
Push — master ( 8d099e...79397a )
by Divine Niiquaye
02:48
created

RouteCollection::create()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
nc 1
nop 2
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Flight\Routing;
19
20
/**
21
 * A RouteCollection represents a set of Route instances.
22
 *
23
 * This class provides all(*) methods for creating path+HTTP method-based routes and
24
 * injecting them into the router:
25
 *
26
 * - head
27
 * - get
28
 * - post
29
 * - put
30
 * - patch
31
 * - delete
32
 * - options
33
 * - any
34
 * - resource
35
 *
36
 * A general `addRoute()` method allows specifying multiple request methods and/or
37
 * arbitrary request methods when creating a path-based route.
38
 *
39
 * @author Divine Niiquaye Ibok <[email protected]>
40
 */
41
class RouteCollection
42
{
43
    use Traits\PrototypeTrait;
44
    use Traits\GroupingTrait;
45
46
    /** @var array<int,Route> */
47
    private array $routes = [];
48
49
    private ?Route $route = null;
50
51
    private bool $locked = false;
52
53
    /**
54
     * @param string $namedPrefix The unqiue name for this group
55
     */
56 142
    public function __construct(string $namedPrefix = null)
57
    {
58 142
        $this->namedPrefix = $namedPrefix;
59
    }
60
61
    /**
62
     * Nested collection and routes should be cloned.
63
     */
64 1
    public function __clone()
65
    {
66 1
        $this->includeRoute(); // Incase of missing end method call on route.
67
68 1
        foreach ($this->routes as $offset => $route) {
69 1
            $this->routes[$offset] = clone $route;
70
        }
71
    }
72
73
    /**
74
     * Inject Groups and sort routes in a natural order.
75
     */
76 138
    final public function buildRoutes(): void
77
    {
78 138
        $this->includeRoute(); // Incase of missing end method call on route.
79 138
        $routes = $this->routes;
80
81 138
        if (!empty($this->groups)) {
82 12
            $this->injectGroups('', $routes);
83
        }
84
85 138
        \usort($routes, static function (Route $a, Route $b): int {
86 46
            return !$a->getStaticPrefix() <=> !$b->getStaticPrefix() ?: \strnatcmp($a->getPath(), $b->getPath());
87
        });
88
89 138
        $this->locked = true; // Lock grouping and prototyping
90 138
        $this->routes = $routes;
91
    }
92
93
    /**
94
     * Get all the routes.
95
     *
96
     * @return array<int,Route>
97
     */
98 137
    public function getRoutes(): array
99
    {
100 137
        if (!$this->locked) {
101 25
            $this->buildRoutes();
102
        }
103
104 137
        return $this->routes;
105
    }
106
107
    /**
108
     * Get the current route in stack.
109
     */
110 6
    public function getRoute(): ?Route
111
    {
112 6
        return $this->route;
113
    }
114
115
    /**
116
     * Add route to the collection.
117
     *
118
     * @return $this
119
     */
120 15
    public function add(Route $route)
121
    {
122 15
        $this->route = $this->injectRoute($route);
123
124 15
        return $this;
125
    }
126
127
    /**
128
     * Maps a pattern to a handler.
129
     *
130
     * You can must specify HTTP methods that should be matched.
131
     *
132
     * @param string   $pattern Matched route pattern
133
     * @param string[] $methods Matched HTTP methods
134
     * @param mixed    $handler Handler that returns the response when matched
135
     *
136
     * @return $this
137
     */
138 32
    public function addRoute(string $pattern, array $methods, $handler = null)
139
    {
140 32
        $this->route = $this->injectRoute(new Route($pattern, $methods, $handler));
141
142 31
        return $this;
143
    }
144
145
    /**
146
     * Add routes to the collection.
147
     *
148
     * @param Route[] $routes
149
     *
150
     * @throws \TypeError        if $routes doesn't contain a route instance
151
     * @throws \RuntimeException if locked
152
     *
153
     * @return $this
154
     */
155 90
    public function routes(array $routes)
156
    {
157 90
        foreach ($routes as $route) {
158 90
            $this->routes[] = $this->injectRoute($route);
159
        }
160
161 90
        return $this;
162
    }
163
164
    /**
165
     * Maps a HEAD request to a handler.
166
     *
167
     * @param string $pattern Matched route pattern
168
     * @param mixed  $handler Handler that returns the response when matched
169
     *
170
     * @return $this
171
     */
172 4
    public function head(string $pattern, $handler = null)
173
    {
174 4
        return $this->addRoute($pattern, [Router::METHOD_HEAD], $handler);
175
    }
176
177
    /**
178
     * Maps a GET and HEAD request to a handler.
179
     *
180
     * @param string $pattern Matched route pattern
181
     * @param mixed  $handler Handler that returns the response when matched
182
     *
183
     * @return $this
184
     */
185 11
    public function get(string $pattern, $handler = null)
186
    {
187 11
        return $this->addRoute($pattern, [Router::METHOD_GET, Router::METHOD_HEAD], $handler);
188
    }
189
190
    /**
191
     * Maps a POST request to a handler.
192
     *
193
     * @param string $pattern Matched route pattern
194
     * @param mixed  $handler Handler that returns the response when matched
195
     *
196
     * @return $this
197
     */
198 2
    public function post(string $pattern, $handler = null)
199
    {
200 2
        return $this->addRoute($pattern, [Router::METHOD_POST], $handler);
201
    }
202
203
    /**
204
     * Maps a PUT request to a handler.
205
     *
206
     * @param string $pattern Matched route pattern
207
     * @param mixed  $handler Handler that returns the response when matched
208
     *
209
     * @return $this
210
     */
211 1
    public function put(string $pattern, $handler = null)
212
    {
213 1
        return $this->addRoute($pattern, [Router::METHOD_PUT], $handler);
214
    }
215
216
    /**
217
     * Maps a PATCH request to a handler.
218
     *
219
     * @param string $pattern Matched route pattern
220
     * @param mixed  $handler Handler that returns the response when matched
221
     *
222
     * @return $this
223
     */
224 2
    public function patch(string $pattern, $handler = null)
225
    {
226 2
        return $this->addRoute($pattern, [Router::METHOD_PATCH], $handler);
227
    }
228
229
    /**
230
     * Maps a DELETE request to a handler.
231
     *
232
     * @param string $pattern Matched route pattern
233
     * @param mixed  $handler Handler that returns the response when matched
234
     *
235
     * @return $this
236
     */
237 1
    public function delete(string $pattern, $handler = null)
238
    {
239 1
        return $this->addRoute($pattern, [Router::METHOD_DELETE], $handler);
240
    }
241
242
    /**
243
     * Maps a OPTIONS request to a handler.
244
     *
245
     * @param string $pattern Matched route pattern
246
     * @param mixed  $handler Handler that returns the response when matched
247
     *
248
     * @return $this
249
     */
250 1
    public function options(string $pattern, $handler = null)
251
    {
252 1
        return $this->addRoute($pattern, [Router::METHOD_OPTIONS], $handler);
253
    }
254
255
    /**
256
     * Maps any request to a handler.
257
     *
258
     * @param string $pattern Matched route pattern
259
     * @param mixed  $handler Handler that returns the response when matched
260
     *
261
     * @return $this
262
     */
263 1
    public function any(string $pattern, $handler = null)
264
    {
265 1
        return $this->addRoute($pattern, Router::HTTP_METHODS_STANDARD, $handler);
266
    }
267
268
    /**
269
     * Maps any Router::HTTP_METHODS_STANDARD request to a resource handler prefixed to $action's method name.
270
     *
271
     * E.g: Having pattern as "/accounts/{userId}", all request made from supported request methods
272
     * are to have the same url.
273
     *
274
     * @param string              $action   The prefixed name attached to request method
275
     * @param string              $pattern  matched path where request should be sent to
276
     * @param class-string|object $resource Handler that returns the response
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|object at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|object.
Loading history...
277
     *
278
     * @return $this
279
     */
280 1
    public function resource(string $pattern, $resource, string $action = 'action')
281
    {
282 1
        return $this->any($pattern, new Handlers\ResourceHandler($resource, $action));
283
    }
284
285
    /**
286
     * @throws \RuntimeException if locked
287
     */
288 133
    protected function injectRoute(Route $route): Route
289
    {
290 133
        if ($this->locked) {
291 1
            throw new \RuntimeException('Cannot add a route to a frozen routes collection.');
292
        }
293
294 132
        $this->includeRoute(); // Incase of missing end method call on route.
295
296 132
        if (!empty($defaultsStack = $this->prototypes)) {
297 10
            foreach ($defaultsStack as $routeMethod => $arguments) {
298 10
                if ('prefix' === $routeMethod) {
299 6
                    $route->prefix(\implode('', \array_merge(...$arguments)));
300
301 6
                    continue;
302
                }
303
304 9
                if (\count($arguments) > 1) {
305 2
                    foreach ($arguments as $argument) {
306 2
                        $route->{$routeMethod}(...$argument);
307
                    }
308
309 2
                    continue;
310
                }
311
312 8
                $route->{$routeMethod}(...$arguments[0]);
313
            }
314
        }
315
316 132
        return $route;
317
    }
318
319
    /**
320
     * Include route to stack if not done.
321
     */
322 138
    protected function includeRoute(self $collection = null): void
323
    {
324 138
        if (null !== $this->route) {
325 41
            $this->defaultIndex = -1;
326
327 41
            $this->routes[] = $this->route; // Incase an end method is missing at the end of a route call.
328 41
            $this->route = null;
329
        }
330
331 138
        if (null !== $collection) {
332 15
            $collection->includeRoute();
333
334 15
            if (empty($collection->routes)) {
335 7
                $collection->defaultIndex = 1;
336
            }
337
338 15
            $collection->prototypes = \array_merge($this->prototypes, $collection->prototypes);
339 15
            $collection->parent = $this;
340
        }
341
    }
342
}
343