RouteHandler   A
last analyzed

Complexity

Total Complexity 20

Size/Duplication

Total Lines 83
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 33
c 7
b 0
f 0
dl 0
loc 83
ccs 33
cts 33
cp 1
rs 10
wmc 20

4 Methods

Rating   Name   Duplication   Size   Complexity  
A resolveArguments() 0 11 4
A negotiateContentType() 0 13 6
A __construct() 0 3 1
B handle() 0 29 9
1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Flight Routing.
5
 *
6
 * PHP version 8.0 and above required
7
 *
8
 * @author    Divine Niiquaye Ibok <[email protected]>
9
 * @copyright 2019 Divine Niiquaye Ibok (https://divinenii.com/)
10
 * @license   https://opensource.org/licenses/BSD-3-Clause License
11
 *
12
 * For the full copyright and license information, please view the LICENSE
13
 * file that was distributed with this source code.
14
 */
15
16
namespace Flight\Routing\Handlers;
17
18
use Flight\Routing\Exceptions\{InvalidControllerException, RouteNotFoundException};
19
use Flight\Routing\Router;
20
use Psr\Http\Message\{ResponseFactoryInterface, ResponseInterface, ServerRequestInterface};
21
use Psr\Http\Server\RequestHandlerInterface;
22
23
/**
24
 * Default routing request handler.
25
 *
26
 * if route is found in request attribute, dispatch the route handler's
27
 * response to the browser and provides ability to detect right response content-type.
28
 *
29
 * @author Divine Niiquaye Ibok <[email protected]>
30
 */
31
class RouteHandler implements RequestHandlerInterface
32
{
33
    /** This allows a response to be served when no route is found. */
34
    public const OVERRIDE_NULL_ROUTE = 'OVERRIDE_NULL_ROUTE';
35
36
    /** @var callable */
37
    protected $handlerResolver;
38
39 84
    public function __construct(protected ResponseFactoryInterface $responseFactory, callable $handlerResolver = null)
40
    {
41 84
        $this->handlerResolver = $handlerResolver ?? new RouteInvoker();
42
    }
43
44
    /**
45
     * {@inheritdoc}
46
     *
47
     * @throws InvalidControllerException|RouteNotFoundException
48
     */
49 83
    public function handle(ServerRequestInterface $request): ResponseInterface
50
    {
51 83
        if (null === $route = $request->getAttribute(Router::class)) {
52 19
            if (true === $res = $request->getAttribute(static::OVERRIDE_NULL_ROUTE)) {
53 10
                return $this->responseFactory->createResponse();
54
            }
55
56 9
            return $res instanceof ResponseInterface ? $res : throw new RouteNotFoundException($request->getUri());
57
        }
58
59 64
        if (empty($handler = $route['handler'] ?? null)) {
60 1
            return $this->responseFactory->createResponse(204)->withHeader('Content-Type', 'text/plain; charset=utf-8');
61
        }
62
63 64
        $arguments = fn (ServerRequestInterface $request): array => $this->resolveArguments($request, $route['arguments'] ?? []);
64 64
        $response = RouteInvoker::resolveRoute($request, $this->handlerResolver, $handler, $arguments);
65
66 62
        if ($response instanceof FileHandler) {
67 1
            return $response($this->responseFactory);
68
        }
69
70 62
        if (!$response instanceof ResponseInterface) {
71 14
            if (empty($contents = $response)) {
72 1
                throw new InvalidControllerException('The route handler\'s content is not a valid PSR7 response body stream.');
73
            }
74 13
            ($response = $this->responseFactory->createResponse())->getBody()->write($contents);
75
        }
76
77 61
        return $response->hasHeader('Content-Type') ? $response : $this->negotiateContentType($response);
78
    }
79
80
    /**
81
     * A HTTP response Content-Type header negotiator for html, json, svg, xml, and plain-text.
82
     */
83 20
    protected function negotiateContentType(ResponseInterface $response): ResponseInterface
84
    {
85 20
        if (empty($contents = (string) $response->getBody())) {
86 8
            $mime = 'text/plain; charset=utf-8';
87 8
            $response = $response->withStatus(204);
88 15
        } elseif (false === $mime = (new \finfo(\FILEINFO_MIME_TYPE))->buffer($contents)) {
89
            $mime = 'text/html; charset=utf-8'; // @codeCoverageIgnore
90 15
        } elseif ('text/xml' === $mime) {
91 3
            \preg_match('/<(?:\s+)?\/?(?:\s+)?(\w+)(?:\s+)?>$/', $contents, $xml, \PREG_UNMATCHED_AS_NULL);
92 3
            $mime = 'svg' === $xml[1] ? 'image/svg+xml' : \sprintf('%s; charset=utf-8', 'rss' === $xml[1] ? 'application/rss+xml' : 'text/xml');
93
        }
94
95 20
        return $response->withHeader('Content-Type', $mime);
96
    }
97
98
    /**
99
     * @param array<string,mixed> $parameters
100
     *
101
     * @return array<int|string,mixed>
102
     */
103 63
    protected function resolveArguments(ServerRequestInterface $request, array $parameters): array
104
    {
105 63
        foreach ([$request, $this->responseFactory] as $psr7) {
106 63
            $parameters[$psr7::class] = $psr7;
107
108 63
            foreach ((@\class_implements($psr7) ?: []) as $psr7Interface) {
109 63
                $parameters[$psr7Interface] = $psr7;
110
            }
111
        }
112
113 63
        return $parameters;
114
    }
115
}
116