Passed
Pull Request — master (#34)
by Anatoly
02:05
created

Router::group()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 4
c 1
b 0
f 0
dl 0
loc 9
ccs 5
cts 5
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
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 Fig\Http\Message\RequestMethodInterface;
18
use Psr\Http\Message\ResponseInterface;
19
use Psr\Http\Message\ServerRequestInterface;
20
use Psr\Http\Server\MiddlewareInterface;
21
use Psr\Http\Server\RequestHandlerInterface;
22
use Sunrise\Http\Router\Exception\ExceptionInterface;
23
use Sunrise\Http\Router\Loader\LoaderInterface;
24
use Sunrise\Http\Router\RequestHandler\QueueableRequestHandler;
25
26
/**
27
 * Import functions
28
 */
29
use function array_keys;
30
use function array_values;
31
use function spl_object_hash;
32
33
/**
34
 * Router
35
 */
36
class Router implements MiddlewareInterface, RequestHandlerInterface, RequestMethodInterface
37
{
38
39
    /**
40
     * Server Request attribute name for routing error instance
41
     *
42
     * @var string
43
     */
44
    public const ATTR_NAME_FOR_ROUTING_ERROR = '@routing-error';
45
46
    /**
47
     * The router routes
48
     *
49
     * @var RouteInterface[]
50
     */
51
    private $routes = [];
52
53
    /**
54
     * The router middlewares
55
     *
56
     * @var MiddlewareInterface[]
57
     */
58
    private $middlewares = [];
59
60
    /**
61
     * Gets the router routes
62
     *
63
     * @return RouteInterface[]
64
     */
65 3
    public function getRoutes() : array
66
    {
67 3
        return array_values($this->routes);
68
    }
69
70
    /**
71
     * Gets the router middlewares
72
     *
73
     * @return MiddlewareInterface[]
74
     */
75 5
    public function getMiddlewares() : array
76
    {
77 5
        return array_values($this->middlewares);
78
    }
79
80
    /**
81
     * Adds the given route(s) to the router
82
     *
83
     * @param RouteInterface ...$routes
84
     *
85
     * @return void
86
     *
87
     * @throws Exception\RouteAlreadyExistsException
88
     */
89 19
    public function addRoute(RouteInterface ...$routes) : void
90
    {
91 19
        foreach ($routes as $route) {
92 19
            $name = $route->getName();
93
94 19
            if (isset($this->routes[$name])) {
95 1
                throw (new ExceptionFactory)->routeAlreadyExists($name, [
96 1
                    'route' => $route,
97
                ]);
98
            }
99
100 19
            $this->routes[$name] = $route;
101
        }
102 19
    }
103
104
    /**
105
     * Adds the given middleware(s) to the router
106
     *
107
     * @param MiddlewareInterface ...$middlewares
108
     *
109
     * @return void
110
     *
111
     * @throws Exception\MiddlewareAlreadyExistsException
112
     */
113 4
    public function addMiddleware(MiddlewareInterface ...$middlewares) : void
114
    {
115 4
        foreach ($middlewares as $middleware) {
116 4
            $hash = spl_object_hash($middleware);
117
118 4
            if (isset($this->middlewares[$hash])) {
119 1
                throw (new ExceptionFactory)->middlewareAlreadyExists($hash, [
120 1
                    'middleware' => $middleware,
121
                ]);
122
            }
123
124 4
            $this->middlewares[$hash] = $middleware;
125
        }
126 4
    }
127
128
    /**
129
     * Gets allowed HTTP methods
130
     *
131
     * @return string[]
132
     */
133 4
    public function getAllowedMethods() : array
134
    {
135 4
        $methods = [];
136 4
        foreach ($this->routes as $route) {
137 4
            foreach ($route->getMethods() as $method) {
138 4
                $methods[$method] = true;
139
            }
140
        }
141
142 4
        return array_keys($methods);
143
    }
144
145
    /**
146
     * Gets a route for the given name
147
     *
148
     * @param string $name
149
     *
150
     * @return RouteInterface
151
     *
152
     * @throws Exception\RouteNotFoundException
153
     */
154 3
    public function getRoute(string $name) : RouteInterface
155
    {
156 3
        if (!isset($this->routes[$name])) {
157 1
            throw (new ExceptionFactory)->routeNotFoundByName($name);
158
        }
159
160 2
        return $this->routes[$name];
161
    }
162
163
    /**
164
     * Generates a URI for the given named route
165
     *
166
     * @param string $name
167
     * @param array $attributes
168
     * @param bool $strict
169
     *
170
     * @return string
171
     *
172
     * @throws Exception\RouteNotFoundException
173
     *
174
     * @throws Exception\InvalidAttributeValueException May be thrown from `path_build`.
175
     * @throws Exception\MissingAttributeValueException May be thrown from `path_build`.
176
     */
177 1
    public function generateUri(string $name, array $attributes = [], bool $strict = false) : string
178
    {
179 1
        $route = $this->getRoute($name);
180
181 1
        $attributes += $route->getAttributes();
182
183 1
        return path_build($route->getPath(), $attributes, $strict);
0 ignored issues
show
Bug introduced by
The function path_build was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

183
        return /** @scrutinizer ignore-call */ path_build($route->getPath(), $attributes, $strict);
Loading history...
184
    }
185
186
    /**
187
     * Looks for a route that matches the given request
188
     *
189
     * @param ServerRequestInterface $request
190
     *
191
     * @return RouteInterface
192
     *
193
     * @throws Exception\MethodNotAllowedException
194
     * @throws Exception\RouteNotFoundException
195
     */
196 11
    public function match(ServerRequestInterface $request) : RouteInterface
197
    {
198 11
        $routes = [];
199 11
        foreach ($this->routes as $route) {
200 11
            foreach ($route->getMethods() as $method) {
201 11
                $routes[$method][] = $route;
202
            }
203
        }
204
205 11
        $requestedMethod = $request->getMethod();
206 11
        if (!isset($routes[$requestedMethod])) {
207 3
            throw (new ExceptionFactory)->methodNotAllowed($requestedMethod, $this->getAllowedMethods(), [
208 3
                'request' => $request,
209
            ]);
210
        }
211
212 8
        $requestedUri = $request->getUri()->getPath();
213 8
        foreach ($routes[$requestedMethod] as $route) {
214 8
            if (path_match($route->getPath(), $requestedUri, $attributes)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $attributes seems to be never defined.
Loading history...
Bug introduced by
The function path_match was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

214
            if (/** @scrutinizer ignore-call */ path_match($route->getPath(), $requestedUri, $attributes)) {
Loading history...
215 8
                return $route->withAddedAttributes($attributes);
216
            }
217
        }
218
219 3
        throw (new ExceptionFactory)->routeNotFoundByUri($requestedUri, [
220 3
            'request' => $request,
221
        ]);
222
    }
223
224
    /**
225
     * {@inheritDoc}
226
     */
227 8
    public function handle(ServerRequestInterface $request) : ResponseInterface
228
    {
229 8
        $route = $this->match($request);
230
231 4
        $handler = new QueueableRequestHandler($route);
232 4
        $handler->add(...$this->getMiddlewares());
233
234 4
        return $handler->handle($request);
235
    }
236
237
    /**
238
     * {@inheritDoc}
239
     */
240 3
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
241
    {
242
        try {
243 3
            return $this->handle($request);
244 2
        } catch (ExceptionInterface $e) {
245 2
            return $handler->handle(
246 2
                $request->withAttribute(self::ATTR_NAME_FOR_ROUTING_ERROR, $e)
247
            );
248
        }
249
    }
250
251
    /**
252
     * Loads routes through the given loaders
253
     *
254
     * @param LoaderInterface ...$loaders
255
     *
256
     * @return void
257
     */
258 2
    public function load(LoaderInterface ...$loaders) : void
259
    {
260 2
        foreach ($loaders as $loader) {
261 2
            $this->addRoute(...$loader->load()->all());
262
        }
263 2
    }
264
}
265