Passed
Push — master ( e1642a...3c4285 )
by Anatoly
01:03 queued 12s
created

RouteCollector::resolveRequestHandler()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 7

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 19
ccs 10
cts 10
cp 1
rs 8.8333
cc 7
nc 5
nop 2
crap 7
1
<?php declare(strict_types=1);
2
3
/**
4
 * It's free open-source software released under the MIT License.
5
 *
6
 * @author Anatoly Fenric <[email protected]>
7
 * @copyright Copyright (c) 2018, Anatoly Fenric
8
 * @license https://github.com/sunrise-php/http-router/blob/master/LICENSE
9
 * @link https://github.com/sunrise-php/http-router
10
 */
11
12
namespace Sunrise\Http\Router;
13
14
/**
15
 * Import classes
16
 */
17
use Closure;
18
use Psr\Container\ContainerInterface;
19
use Psr\Http\Server\MiddlewareInterface;
20
use Psr\Http\Server\RequestHandlerInterface;
21
use Sunrise\Http\Router\Exception\UnresolvableObjectException;
22
use Sunrise\Http\Router\Middleware\CallableMiddleware;
23
use Sunrise\Http\Router\RequestHandler\CallableRequestHandler;
24
25
/**
26
 * Import functions
27
 */
28
use function is_string;
29
use function is_subclass_of;
30
use function sprintf;
31
32
/**
33
 * RouteCollector
34
 */
35
class RouteCollector
36
{
37
38
    /**
39
     * Route collection factory of the collector
40
     *
41
     * @var RouteCollectionFactoryInterface
42
     */
43
    private $collectionFactory;
44
45
    /**
46
     * Route factory of the collector
47
     *
48
     * @var RouteFactoryInterface
49
     */
50
    private $routeFactory;
51
52
    /**
53
     * Route collection of the collector
54
     *
55
     * @var RouteCollectionInterface
56
     */
57
    private $collection;
58
59
    /**
60
     * The collector container
61
     *
62
     * @var null|ContainerInterface
63
     */
64
    private $container = null;
65
66
    /**
67
     * Constructor of the class
68
     *
69
     * @param null|RouteCollectionFactoryInterface $collectionFactory
70
     * @param null|RouteFactoryInterface $routeFactory
71
     */
72 35
    public function __construct(
73
        ?RouteCollectionFactoryInterface $collectionFactory = null,
74
        ?RouteFactoryInterface $routeFactory = null
75
    ) {
76 35
        $this->collectionFactory = $collectionFactory ?? new RouteCollectionFactory();
77 35
        $this->routeFactory = $routeFactory ?? new RouteFactory();
78
79 35
        $this->collection = $this->collectionFactory->createCollection();
80 35
    }
81
82
    /**
83
     * Gets the collector collection
84
     *
85
     * @return RouteCollectionInterface
86
     */
87 7
    public function getCollection() : RouteCollectionInterface
88
    {
89 7
        return $this->collection;
90
    }
91
92
    /**
93
     * Gets the collector container
94
     *
95
     * @return null|ContainerInterface
96
     *
97
     * @since 2.9.0
98
     */
99 1
    public function getContainer() : ?ContainerInterface
100
    {
101 1
        return $this->container;
102
    }
103
104
    /**
105
     * Sets the given container to the collector
106
     *
107
     * @param null|ContainerInterface $container
108
     *
109
     * @return void
110
     *
111
     * @since 2.9.0
112
     */
113 9
    public function setContainer(?ContainerInterface $container) : void
114
    {
115 9
        $this->container = $container;
116 9
    }
117
118
    /**
119
     * Makes a new route from the given parameters
120
     *
121
     * @param string $name
122
     * @param string $path
123
     * @param string[] $methods
124
     * @param mixed $requestHandler
125
     * @param array $middlewares
126
     * @param array $attributes
127
     *
128
     * @return RouteInterface
129
     */
130 32
    public function route(
131
        string $name,
132
        string $path,
133
        array $methods,
134
        $requestHandler,
135
        array $middlewares = [],
136
        array $attributes = []
137
    ) : RouteInterface {
138 32
        $route = $this->routeFactory->createRoute(
139 32
            $name,
140
            $path,
141
            $methods,
142 32
            $this->resolveRequestHandler($name, $requestHandler),
143 32
            $this->resolveMiddlewares($name, $middlewares),
144
            $attributes
145
        );
146
147 32
        $this->collection->add($route);
148
149 32
        return $route;
150
    }
151
152
    /**
153
     * Makes a new route that will respond to HEAD requests
154
     *
155
     * @param string $name
156
     * @param string $path
157
     * @param mixed $requestHandler
158
     * @param array $middlewares
159
     * @param array $attributes
160
     *
161
     * @return RouteInterface
162
     */
163 3
    public function head(
164
        string $name,
165
        string $path,
166
        $requestHandler,
167
        array $middlewares = [],
168
        array $attributes = []
169
    ) : RouteInterface {
170 3
        return $this->route(
171 3
            $name,
172
            $path,
173 3
            [Router::METHOD_HEAD],
174
            $requestHandler,
175
            $middlewares,
176
            $attributes
177
        );
178
    }
179
180
    /**
181
     * Makes a new route that will respond to GET requests
182
     *
183
     * @param string $name
184
     * @param string $path
185
     * @param mixed $requestHandler
186
     * @param array $middlewares
187
     * @param array $attributes
188
     *
189
     * @return RouteInterface
190
     */
191 10
    public function get(
192
        string $name,
193
        string $path,
194
        $requestHandler,
195
        array $middlewares = [],
196
        array $attributes = []
197
    ) : RouteInterface {
198 10
        return $this->route(
199 10
            $name,
200
            $path,
201 10
            [Router::METHOD_GET],
202
            $requestHandler,
203
            $middlewares,
204
            $attributes
205
        );
206
    }
207
208
    /**
209
     * Makes a new route that will respond to POST requests
210
     *
211
     * @param string $name
212
     * @param string $path
213
     * @param mixed $requestHandler
214
     * @param array $middlewares
215
     * @param array $attributes
216
     *
217
     * @return RouteInterface
218
     */
219 4
    public function post(
220
        string $name,
221
        string $path,
222
        $requestHandler,
223
        array $middlewares = [],
224
        array $attributes = []
225
    ) : RouteInterface {
226 4
        return $this->route(
227 4
            $name,
228
            $path,
229 4
            [Router::METHOD_POST],
230
            $requestHandler,
231
            $middlewares,
232
            $attributes
233
        );
234
    }
235
236
    /**
237
     * Makes a new route that will respond to PUT requests
238
     *
239
     * @param string $name
240
     * @param string $path
241
     * @param mixed $requestHandler
242
     * @param array $middlewares
243
     * @param array $attributes
244
     *
245
     * @return RouteInterface
246
     */
247 3
    public function put(
248
        string $name,
249
        string $path,
250
        $requestHandler,
251
        array $middlewares = [],
252
        array $attributes = []
253
    ) : RouteInterface {
254 3
        return $this->route(
255 3
            $name,
256
            $path,
257 3
            [Router::METHOD_PUT],
258
            $requestHandler,
259
            $middlewares,
260
            $attributes
261
        );
262
    }
263
264
    /**
265
     * Makes a new route that will respond to PATCH requests
266
     *
267
     * @param string $name
268
     * @param string $path
269
     * @param mixed $requestHandler
270
     * @param array $middlewares
271
     * @param array $attributes
272
     *
273
     * @return RouteInterface
274
     */
275 4
    public function patch(
276
        string $name,
277
        string $path,
278
        $requestHandler,
279
        array $middlewares = [],
280
        array $attributes = []
281
    ) : RouteInterface {
282 4
        return $this->route(
283 4
            $name,
284
            $path,
285 4
            [Router::METHOD_PATCH],
286
            $requestHandler,
287
            $middlewares,
288
            $attributes
289
        );
290
    }
291
292
    /**
293
     * Makes a new route that will respond to DELETE requests
294
     *
295
     * @param string $name
296
     * @param string $path
297
     * @param mixed $requestHandler
298
     * @param array $middlewares
299
     * @param array $attributes
300
     *
301
     * @return RouteInterface
302
     */
303 3
    public function delete(
304
        string $name,
305
        string $path,
306
        $requestHandler,
307
        array $middlewares = [],
308
        array $attributes = []
309
    ) : RouteInterface {
310 3
        return $this->route(
311 3
            $name,
312
            $path,
313 3
            [Router::METHOD_DELETE],
314
            $requestHandler,
315
            $middlewares,
316
            $attributes
317
        );
318
    }
319
320
    /**
321
     * Makes a new route that will respond to PURGE requests
322
     *
323
     * @param string $name
324
     * @param string $path
325
     * @param mixed $requestHandler
326
     * @param array $middlewares
327
     * @param array $attributes
328
     *
329
     * @return RouteInterface
330
     */
331 3
    public function purge(
332
        string $name,
333
        string $path,
334
        $requestHandler,
335
        array $middlewares = [],
336
        array $attributes = []
337
    ) : RouteInterface {
338 3
        return $this->route(
339 3
            $name,
340
            $path,
341 3
            [Router::METHOD_PURGE],
342
            $requestHandler,
343
            $middlewares,
344
            $attributes
345
        );
346
    }
347
348
    /**
349
     * Route grouping logic
350
     *
351
     * @param callable $callback
352
     *
353
     * @return RouteCollectionInterface
354
     */
355 2
    public function group(callable $callback) : RouteCollectionInterface
356
    {
357 2
        $collector = new self(
358 2
            $this->collectionFactory,
359 2
            $this->routeFactory
360
        );
361
362 2
        $collector->setContainer($this->container);
363
364 2
        $callback($collector);
365
366 2
        $this->collection->add(...$collector->collection->all());
367
368 2
        return $collector->collection;
369
    }
370
371
    /**
372
     * Tries to resolve the given request handler
373
     *
374
     * @param string $routeName
375
     * @param mixed $requestHandler
376
     *
377
     * @return RequestHandlerInterface
378
     *
379
     * @throws UnresolvableObjectException
380
     *
381
     * @since 2.9.0
382
     *
383
     * @todo Maybe move to a new abstract layer and think about deeper integration into the router...
384
     */
385 32
    private function resolveRequestHandler(string $routeName, $requestHandler) : RequestHandlerInterface
386
    {
387 32
        if ($requestHandler instanceof RequestHandlerInterface) {
388 31
            return $requestHandler;
389
        }
390
391 1
        if ($requestHandler instanceof Closure) {
392 1
            return new CallableRequestHandler($requestHandler);
393
        }
394
395 1
        if (!is_string($requestHandler) || !is_subclass_of($requestHandler, RequestHandlerInterface::class)) {
396 1
            throw new UnresolvableObjectException(sprintf('Route %s refers to invalid request handler.', $routeName));
397
        }
398
399 1
        if ($this->container && $this->container->has($requestHandler)) {
400 1
            return $this->container->get($requestHandler);
401
        }
402
403 1
        return new $requestHandler;
404
    }
405
406
    /**
407
     * Tries to resolve the given middleware
408
     *
409
     * @param string $routeName
410
     * @param mixed $middleware
411
     *
412
     * @return MiddlewareInterface
413
     *
414
     * @throws UnresolvableObjectException
415
     *
416
     * @since 2.9.0
417
     *
418
     * @todo Maybe move to a new abstract layer and think about deeper integration into the router...
419
     */
420 9
    private function resolveMiddleware(string $routeName, $middleware) : MiddlewareInterface
421
    {
422 9
        if ($middleware instanceof MiddlewareInterface) {
423 8
            return $middleware;
424
        }
425
426 1
        if ($middleware instanceof Closure) {
427 1
            return new CallableMiddleware($middleware);
428
        }
429
430 1
        if (!is_string($middleware) || !is_subclass_of($middleware, MiddlewareInterface::class)) {
431 1
            throw new UnresolvableObjectException(sprintf('Route %s refers to invalid middleware.', $routeName));
432
        }
433
434 1
        if ($this->container && $this->container->has($middleware)) {
435 1
            return $this->container->get($middleware);
436
        }
437
438 1
        return new $middleware;
439
    }
440
441
    /**
442
     * Tries to resolve the given middlewares
443
     *
444
     * @param string $routeName
445
     * @param array $middlewares
446
     *
447
     * @return MiddlewareInterface[]
448
     *
449
     * @throws UnresolvableObjectException
450
     *
451
     * @since 2.9.0
452
     *
453
     * @todo Maybe move to a new abstract layer and think about deeper integration into the router...
454
     */
455 32
    private function resolveMiddlewares(string $routeName, array $middlewares) : array
456
    {
457 32
        if (empty($middlewares)) {
458 23
            return [];
459
        }
460
461 9
        foreach ($middlewares as &$middleware) {
462 9
            $middleware = $this->resolveMiddleware($routeName, $middleware);
463
        }
464
465 9
        return $middlewares;
466
    }
467
}
468