Test Failed
Pull Request — master (#26)
by Divine Niiquaye
02:27
created

RouteCollection::includeRoute()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 10
c 0
b 0
f 0
nc 6
nop 1
dl 0
loc 18
ccs 7
cts 7
cp 1
crap 4
rs 9.9332
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
    public function __construct(string $namedPrefix = null)
57
    {
58
        $this->namedPrefix = $namedPrefix;
59
    }
60
61
    /**
62
     * Nested collection and routes should be cloned.
63
     */
64
    public function __clone()
65
    {
66
        $this->includeRoute(); // Incase of missing end method call on route.
67
68
        foreach ($this->routes as $offset => $route) {
69
            $this->routes[$offset] = clone $route;
70
        }
71
    }
72
73
    /**
74
     * Inject Groups and sort routes in a natural order.
75
     */
76
    final public function buildRoutes(): void
77
    {
78
        $this->includeRoute(); // Incase of missing end method call on route.
79 13
        $routes = $this->routes;
80
81 13
        if (!empty($this->groups)) {
82 13
            $this->injectGroups('', $routes);
83
        }
84 13
85 1
        \usort($routes, static function (Route $a, Route $b): int {
86 1
            return !$a->getStaticPrefix() <=> !$b->getStaticPrefix() ?: \strnatcmp($a->getPath(), $b->getPath());
87
        });
88
89 1
        $this->locked = true; // Lock grouping and prototyping
90
        $this->routes = $routes;
91 12
    }
92 12
93
    /**
94
     * Get all the routes.
95
     *
96 11
     * @return array<int,Route>
97
     */
98
    public function getRoutes(): array
99
    {
100
        if (!$this->locked) {
101
            $this->buildRoutes();
102
        }
103
104 5
        return $this->routes;
105
    }
106 5
107
    /**
108
     * Get the current route in stack.
109
     */
110
    public function getRoute(): ?Route
111
    {
112
        return $this->route;
113
    }
114
115
    /**
116 81
     * Add route to the collection.
117
     *
118 81
     * @return $this
119
     */
120
    public function add(Route $route)
121
    {
122
        $this->route = $this->injectRoute($route);
123
124
        return $this;
125
    }
126
127
    /**
128
     * Maps a pattern to a handler.
129
     *
130 70
     * You can must specify HTTP methods that should be matched.
131
     *
132 70
     * @param string   $pattern Matched route pattern
133 70
     * @param string[] $methods Matched HTTP methods
134
     * @param mixed    $handler Handler that returns the response when matched
135 70
     *
136
     * @return $this
137
     */
138 70
    public function addRoute(string $pattern, array $methods, $handler = null)
139
    {
140
        $this->route = $this->injectRoute(new Route($pattern, $methods, $handler));
141
142
        return $this;
143
    }
144
145
    /**
146
     * Add routes to the collection.
147
     *
148
     * @param Route[] $routes
149
     *
150 29
     * @throws \TypeError        if $routes doesn't contain a route instance
151
     * @throws \RuntimeException if locked
152 29
     *
153
     * @return $this
154 29
     */
155
    public function routes(array $routes)
156 29
    {
157
        foreach ($routes as $route) {
158
            $this->routes[] = $this->injectRoute($route);
159
        }
160
161
        return $this;
162
    }
163
164
    /**
165
     * Maps a HEAD request to a handler.
166
     *
167 10
     * @param string $pattern Matched route pattern
168
     * @param mixed  $handler Handler that returns the response when matched
169 10
     *
170 1
     * @return $this
171 1
     */
172
    public function head(string $pattern, $handler = null)
173
    {
174 10
        return $this->addRoute($pattern, [Router::METHOD_HEAD], $handler);
175 3
    }
176 3
177
    /**
178 3
     * Maps a GET and HEAD request to a handler.
179 3
     *
180 7
     * @param string $pattern Matched route pattern
181 1
     * @param mixed  $handler Handler that returns the response when matched
182
     *
183
     * @return $this
184 9
     */
185
    public function get(string $pattern, $handler = null)
186 9
    {
187 9
        return $this->addRoute($pattern, [Router::METHOD_GET, Router::METHOD_HEAD], $handler);
188
    }
189 9
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 1
     *
196
     * @return $this
197
     */
198 1
    public function post(string $pattern, $handler = null)
199 1
    {
200
        return $this->addRoute($pattern, [Router::METHOD_POST], $handler);
201
    }
202 1
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 5
    public function put(string $pattern, $handler = null)
212
    {
213 5
        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 10
     * @return $this
223
     */
224 10
    public function patch(string $pattern, $handler = null)
225
    {
226
        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 2
     * @param mixed  $handler Handler that returns the response when matched
234
     *
235 2
     * @return $this
236
     */
237
    public function delete(string $pattern, $handler = null)
238
    {
239
        return $this->addRoute($pattern, [Router::METHOD_DELETE], $handler);
240
    }
241
242
    /**
243
     * Maps a OPTIONS request to a handler.
244 1
     *
245
     * @param string $pattern Matched route pattern
246 1
     * @param mixed  $handler Handler that returns the response when matched
247
     *
248
     * @return $this
249
     */
250
    public function options(string $pattern, $handler = null)
251
    {
252
        return $this->addRoute($pattern, [Router::METHOD_OPTIONS], $handler);
253
    }
254
255 2
    /**
256
     * Maps any request to a handler.
257 2
     *
258
     * @param string $pattern Matched route pattern
259
     * @param mixed  $handler Handler that returns the response when matched
260
     *
261
     * @return $this
262
     */
263
    public function any(string $pattern, $handler = null)
264
    {
265
        return $this->addRoute($pattern, Router::HTTP_METHODS_STANDARD, $handler);
266 1
    }
267
268 1
    /**
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 1
     *
278
     * @return $this
279 1
     */
280
    public function resource(string $pattern, $resource, string $action = 'action')
281
    {
282
        return $this->any($pattern, new Handlers\ResourceHandler($resource, $action));
283
    }
284
285
    /**
286
     * @throws \RuntimeException if locked
287
     */
288 3
    protected function injectRoute(Route $route): Route
289
    {
290 3
        if ($this->locked) {
291
            throw new \RuntimeException('Cannot add a route to a frozen routes collection.');
292
        }
293
294
        $this->includeRoute(); // Incase of missing end method call on route.
295
296
        if (!empty($defaultsStack = $this->prototypes)) {
297
            foreach ($defaultsStack as $routeMethod => $arguments) {
298
                if ('prefix' === $routeMethod) {
299
                    $route->prefix(\implode('', \array_merge(...$arguments)));
300
301
                    continue;
302 2
                }
303
304 2
                if (\count($arguments) > 1) {
305
                    foreach ($arguments as $argument) {
306
                        $route->{$routeMethod}(...$argument);
307
                    }
308
309
                    continue;
310
                }
311
312
                $route->{$routeMethod}(...$arguments[0]);
313
            }
314 71
        }
315
316
        return $route;
317 71
    }
318 26
319 6
    /**
320
     * Include route to stack if not done.
321
     */
322
    protected function includeRoute(self $collection = null): void
323 71
    {
324
        if (null !== $this->route) {
325
            $this->defaultIndex = -1;
326
327
            $this->routes[] = $this->route; // Incase an end method is missing at the end of a route call.
328
            $this->route = null;
329 92
        }
330
331 92
        if (null !== $collection) {
332 1
            $collection->includeRoute();
333
334 1
            if (empty($collection->routes)) {
335 1
                $collection->defaultIndex = 1;
336
            }
337
338
            $collection->prototypes = \array_merge($this->prototypes, $collection->prototypes);
339 92
            $collection->parent = $this;
340 2
        }
341
    }
342
}
343