Passed
Push — master ( 412c5f...ce13c3 )
by Divine Niiquaye
02:22
created

Router   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 160
Duplicated Lines 0 %

Test Coverage

Coverage 98.28%

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 58
c 8
b 0
f 0
dl 0
loc 160
ccs 57
cts 58
cp 0.9828
rs 10
wmc 17

5 Methods

Rating   Name   Duplication   Size   Complexity  
A match() 0 29 5
A handle() 0 27 4
A __construct() 0 10 1
A generateUri() 0 15 2
A addRoute() 0 20 5
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.1 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
use DivineNii\Invoker\Interfaces\InvokerInterface;
21
use DivineNii\Invoker\Invoker;
22
use Flight\Routing\Exceptions\DuplicateRouteException;
23
use Flight\Routing\Exceptions\MethodNotAllowedException;
24
use Flight\Routing\Exceptions\RouteNotFoundException;
25
use Flight\Routing\Exceptions\UriHandlerException;
26
use Flight\Routing\Exceptions\UrlGenerationException;
27
use Flight\Routing\Interfaces\RouteInterface;
28
use Flight\Routing\Interfaces\RouteMatcherInterface;
29
use Psr\Http\Message\ResponseFactoryInterface;
30
use Psr\Http\Message\ResponseInterface;
31
use Psr\Http\Message\ServerRequestInterface;
32
use Psr\Http\Message\UriFactoryInterface;
33
use Psr\Http\Message\UriInterface;
34
use Psr\Http\Server\RequestHandlerInterface;
35
36
/**
37
 * Router
38
 */
39
class Router implements RequestHandlerInterface
40
{
41
    use Traits\RouterTrait;
42
43
    public const TYPE_REQUIREMENT = 1;
44
45
    public const TYPE_DEFAULT = 0;
46
47 77
    public function __construct(
48
        ResponseFactoryInterface $responseFactory,
49
        UriFactoryInterface $uriFactory,
50
        ?RouteMatcherInterface $matcher = null,
51
        ?InvokerInterface $resolver = null
52
    ) {
53 77
        $this->uriFactory      = $uriFactory;
54 77
        $this->responseFactory = $responseFactory;
55 77
        $this->resolver        = $resolver ?? new Invoker();
56 77
        $this->matcher         = $matcher ?? new Matchers\SimpleRouteMatcher();
57 77
    }
58
59
    /**
60
     * Adds the given route(s) to the router
61
     *
62
     * @param RouteInterface ...$routes
63
     *
64
     * @throws DuplicateRouteException
65
     */
66 62
    public function addRoute(RouteInterface ...$routes): void
67
    {
68 62
        if (!empty($this->cachedRoutes)) {
69
            return;
70
        }
71
72 62
        foreach ($routes as $route) {
73 62
            $name = $route->getName();
74
75 62
            if (isset($this->routes[$name])) {
76 1
                throw new DuplicateRouteException(
77 1
                    \sprintf('A route with the name "%s" already exists.', $name)
78
                );
79
            }
80
81 62
            if ($this->profiler instanceof DebugRoute) {
82 1
                $this->profiler->addProfile(new DebugRoute($name, $route));
83
            }
84
85 62
            $this->routes[$name] = $this->mergeAttributes($route);
86
        }
87 62
    }
88
89
    /**
90
     * Generate a URI from the named route.
91
     *
92
     * Takes the named route and any parameters, and attempts to generate a
93
     * URI from it. Additional router-dependent query may be passed.
94
     *
95
     * Once there are no missing parameters in the URI we will encode
96
     * the URI and prepare it for returning to the user. If the URI is supposed to
97
     * be absolute, we will return it as-is. Otherwise we will remove the URL's root.
98
     *
99
     * @param string                       $routeName   route name
100
     * @param array<string,string>         $parameters  key => value option pairs to pass to the
101
     *                                                  router for purposes of generating a URI; takes precedence over options
102
     *                                                  present in route used to generate URI
103
     * @param array<int|string,int|string> $queryParams Optional query string parameters
104
     *
105
     * @throws UrlGenerationException if the route name is not known
106
     *                                or a parameter value does not match its regex
107
     *
108
     * @return UriInterface of fully qualified URL for named route
109
     */
110 4
    public function generateUri(string $routeName, array $parameters = [], array $queryParams = []): UriInterface
111
    {
112
        try {
113 4
            $route = $this->getRoute($routeName);
114 1
        } catch (RouteNotFoundException $e) {
115 1
            throw new UrlGenerationException(
116 1
                \sprintf(
117 1
                    'Unable to generate a URL for the named route "%s" as such route does not exist.',
118 1
                    $routeName
119
                ),
120 1
                404
121
            );
122
        }
123
124 3
        return $this->uriFactory->createUri($this->resolveUri($route, $parameters, $queryParams));
125
    }
126
127
    /**
128
     * Looks for a route that matches the given request
129
     *
130
     * @param ServerRequestInterface $request
131
     *
132
     * @throws MethodNotAllowedException
133
     * @throws UriHandlerException
134
     * @throws RouteNotFoundException
135
     *
136
     * @return RouteInterface
137
     */
138 49
    public function match(ServerRequestInterface $request): RouteInterface
139
    {
140
        // Get the request matching format.
141 49
        $route = $this->matcher->matchRoutes($this, $request);
142
143 45
        if (!$route instanceof RouteInterface) {
144 4
            throw new RouteNotFoundException(
145 4
                \sprintf(
146 4
                    'Unable to find the controller for path "%s". The route is wrongly configured.',
147 4
                    $request->getUri()->getPath()
148
                )
149
            );
150
        }
151
152 41
        $route->setController($this->resolveController($request, $route));
153 41
        $route->setArguments($this->mergeDefaults($route->getArguments(), $route->getDefaults()));
154
155
        // Run listeners on route not more than once ...
156 41
        if (null === $this->route) {
157 41
            foreach ($this->listeners as $listener) {
158 1
                $listener->onRoute($request, $route);
159
            }
160
        }
161
162 41
        if ($this->profiler instanceof DebugRoute) {
163 1
            $this->profiler->setMatched($route->getName());
164
        }
165
166 41
        return $this->route = clone $route;
167
    }
168
169
    /**
170
     * {@inheritDoc}
171
     */
172 44
    public function handle(ServerRequestInterface $request): ResponseInterface
173
    {
174
        // Get the Route Handler ready for dispatching
175 44
        if (!$this->route instanceof RouteInterface) {
176 43
            $this->match($request);
177
        }
178
179 38
        return ($middleDispatcher = new Middlewares\MiddlewareDispatcher($this->resolver->getContainer()))->dispatch(
180 38
            $this->getMiddlewares(),
181 38
            new Handlers\CallbackHandler(
182 38
                function (ServerRequestInterface $request) use ($middleDispatcher): ResponseInterface {
183
                    try {
184 38
                        $route   = $request->getAttribute(Route::class, $this->route);
185 38
                        $handler = $this->resolveRoute($route);
186 38
                        $mididlewars = $this->resolveMiddlewares($route);
187
188 38
                        return $middleDispatcher->dispatch($mididlewars, $handler, $request);
189
                    } finally {
190 38
                        if ($this->profiler instanceof DebugRoute) {
191 38
                            foreach ($this->profiler->getProfiles() as $profiler) {
192 1
                                $profiler->leave();
193
                            }
194
                        }
195
                    }
196 38
                }
197
            ),
198 38
            $request->withAttribute(Route::class, $this->route)
199
        );
200
    }
201
}
202