Test Failed
Pull Request — master (#74)
by Anatoly
05:22 queued 02:58
created

RouteCollector::resolveMiddleware()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 19
ccs 0
cts 0
cp 0
rs 8.8333
cc 7
nc 5
nop 2
crap 56
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 31
     * Route collection of the collector
54
     *
55
     * @var RouteCollectionInterface
56
     */
57 31
    private $collection;
58 31
59
    /**
60 31
     * The collector container
61 31
     *
62
     * @var null|ContainerInterface
63
     */
64
    private $container = null;
65
66
    /**
67
     * Constructor of the class
68 6
     *
69
     * @param null|RouteCollectionFactoryInterface $collectionFactory
70 6
     * @param null|RouteFactoryInterface $routeFactory
71
     */
72
    public function __construct(
73
        ?RouteCollectionFactoryInterface $collectionFactory = null,
74
        ?RouteFactoryInterface $routeFactory = null
75
    ) {
76
        $this->collectionFactory = $collectionFactory ?? new RouteCollectionFactory();
77
        $this->routeFactory = $routeFactory ?? new RouteFactory();
78
79
        $this->collection = $this->collectionFactory->createCollection();
80
    }
81
82
    /**
83
     * Gets the collector collection
84
     *
85 29
     * @return RouteCollectionInterface
86
     */
87
    public function getCollection() : RouteCollectionInterface
88
    {
89
        return $this->collection;
90
    }
91
92
    /**
93 29
     * Gets the collector container
94 29
     *
95
     * @return null|ContainerInterface
96
     *
97
     * @since 2.9.0
98
     */
99
    public function getContainer() : ?ContainerInterface
100
    {
101
        return $this->container;
102 29
    }
103
104 29
    /**
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
    public function setContainer(?ContainerInterface $container) : void
114
    {
115
        $this->container = $container;
116
    }
117
118 3
    /**
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 3
     * @param array $middlewares
126 3
     * @param array $attributes
127
     *
128 3
     * @return RouteInterface
129
     */
130
    public function route(
131
        string $name,
132
        string $path,
133
        array $methods,
134
        $requestHandler,
135
        array $middlewares = [],
136
        array $attributes = []
137
    ) : RouteInterface {
138
        $route = $this->routeFactory->createRoute(
139
            $name,
140
            $path,
141
            $methods,
142
            $this->resolveRequestHandler($name, $requestHandler),
143
            $this->resolveMiddlewares($name, $middlewares),
144
            $attributes
145
        );
146 7
147
        $this->collection->add($route);
148
149
        return $route;
150
    }
151
152
    /**
153 7
     * Makes a new route that will respond to HEAD requests
154 7
     *
155
     * @param string $name
156 7
     * @param string $path
157
     * @param mixed $requestHandler
158
     * @param array $middlewares
159
     * @param array $attributes
160
     *
161
     * @return RouteInterface
162
     */
163
    public function head(
164
        string $name,
165
        string $path,
166
        $requestHandler,
167
        array $middlewares = [],
168
        array $attributes = []
169
    ) : RouteInterface {
170
        return $this->route(
171
            $name,
172
            $path,
173
            [Router::METHOD_HEAD],
174 4
            $requestHandler,
175
            $middlewares,
176
            $attributes
177
        );
178
    }
179
180
    /**
181 4
     * Makes a new route that will respond to GET requests
182 4
     *
183
     * @param string $name
184 4
     * @param string $path
185
     * @param mixed $requestHandler
186
     * @param array $middlewares
187
     * @param array $attributes
188
     *
189
     * @return RouteInterface
190
     */
191
    public function get(
192
        string $name,
193
        string $path,
194
        $requestHandler,
195
        array $middlewares = [],
196
        array $attributes = []
197
    ) : RouteInterface {
198
        return $this->route(
199
            $name,
200
            $path,
201
            [Router::METHOD_GET],
202 3
            $requestHandler,
203
            $middlewares,
204
            $attributes
205
        );
206
    }
207
208
    /**
209 3
     * Makes a new route that will respond to POST requests
210 3
     *
211
     * @param string $name
212 3
     * @param string $path
213
     * @param mixed $requestHandler
214
     * @param array $middlewares
215
     * @param array $attributes
216
     *
217
     * @return RouteInterface
218
     */
219
    public function post(
220
        string $name,
221
        string $path,
222
        $requestHandler,
223
        array $middlewares = [],
224
        array $attributes = []
225
    ) : RouteInterface {
226
        return $this->route(
227
            $name,
228
            $path,
229
            [Router::METHOD_POST],
230 4
            $requestHandler,
231
            $middlewares,
232
            $attributes
233
        );
234
    }
235
236
    /**
237 4
     * Makes a new route that will respond to PUT requests
238 4
     *
239
     * @param string $name
240 4
     * @param string $path
241
     * @param mixed $requestHandler
242
     * @param array $middlewares
243
     * @param array $attributes
244
     *
245
     * @return RouteInterface
246
     */
247
    public function put(
248
        string $name,
249
        string $path,
250
        $requestHandler,
251
        array $middlewares = [],
252
        array $attributes = []
253
    ) : RouteInterface {
254
        return $this->route(
255
            $name,
256
            $path,
257
            [Router::METHOD_PUT],
258 3
            $requestHandler,
259
            $middlewares,
260
            $attributes
261
        );
262
    }
263
264
    /**
265 3
     * Makes a new route that will respond to PATCH requests
266 3
     *
267
     * @param string $name
268 3
     * @param string $path
269
     * @param mixed $requestHandler
270
     * @param array $middlewares
271
     * @param array $attributes
272
     *
273
     * @return RouteInterface
274
     */
275
    public function patch(
276
        string $name,
277
        string $path,
278
        $requestHandler,
279
        array $middlewares = [],
280
        array $attributes = []
281
    ) : RouteInterface {
282
        return $this->route(
283
            $name,
284
            $path,
285
            [Router::METHOD_PATCH],
286 3
            $requestHandler,
287
            $middlewares,
288
            $attributes
289
        );
290
    }
291
292
    /**
293 3
     * Makes a new route that will respond to DELETE requests
294 3
     *
295
     * @param string $name
296 3
     * @param string $path
297
     * @param mixed $requestHandler
298
     * @param array $middlewares
299
     * @param array $attributes
300
     *
301
     * @return RouteInterface
302
     */
303
    public function delete(
304
        string $name,
305
        string $path,
306
        $requestHandler,
307
        array $middlewares = [],
308
        array $attributes = []
309
    ) : RouteInterface {
310 2
        return $this->route(
311
            $name,
312 2
            $path,
313 2
            [Router::METHOD_DELETE],
314 2
            $requestHandler,
315
            $middlewares,
316
            $attributes
317 2
        );
318
    }
319 2
320
    /**
321 2
     * 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
    public function purge(
332
        string $name,
333
        string $path,
334
        $requestHandler,
335
        array $middlewares = [],
336
        array $attributes = []
337
    ) : RouteInterface {
338
        return $this->route(
339
            $name,
340
            $path,
341
            [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
    public function group(callable $callback) : RouteCollectionInterface
356
    {
357
        $collector = new self(
358
            $this->collectionFactory,
359
            $this->routeFactory
360
        );
361
362
        $collector->setContainer($this->container);
363
364
        $callback($collector);
365
366
        $this->collection->add(...$collector->collection->all());
367
368
        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
    private function resolveRequestHandler(string $routeName, $requestHandler) : RequestHandlerInterface
386
    {
387
        if ($requestHandler instanceof RequestHandlerInterface) {
388
            return $requestHandler;
389
        }
390
391
        if ($requestHandler instanceof Closure) {
392
            return new CallableRequestHandler($requestHandler);
393
        }
394
395
        if (!is_string($requestHandler) || !is_subclass_of($requestHandler, RequestHandlerInterface::class)) {
396
            throw new UnresolvableObjectException(sprintf('Route %s refers to invalid request handler.', $routeName));
397
        }
398
399
        if ($this->container && $this->container->has($requestHandler)) {
400
            return $this->container->get($requestHandler);
401
        }
402
403
        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
    private function resolveMiddleware(string $routeName, $middleware) : MiddlewareInterface
421
    {
422
        if ($middleware instanceof MiddlewareInterface) {
423
            return $middleware;
424
        }
425
426
        if ($middleware instanceof Closure) {
427
            return new CallableMiddleware($middleware);
428
        }
429
430
        if (!is_string($middleware) || !is_subclass_of($middleware, MiddlewareInterface::class)) {
431
            throw new UnresolvableObjectException(sprintf('Route %s refers to invalid middleware.', $routeName));
432
        }
433
434
        if ($this->container && $this->container->has($middleware)) {
435
            return $this->container->get($middleware);
436
        }
437
438
        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
    private function resolveMiddlewares(string $routeName, array $middlewares) : array
456
    {
457
        if (empty($middlewares)) {
458
            return [];
459
        }
460
461
        foreach ($middlewares as &$middleware) {
462
            $middleware = $this->resolveMiddleware($routeName, $middleware);
463
        }
464
465
        return $middlewares;
466
    }
467
}
468