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

Router::getRoutes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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

193
        return /** @scrutinizer ignore-call */ path_build($route->getPath(), $attributes, $strict);
Loading history...
194
    }
195
196
    /**
197
     * Looks for a route that matches the given request
198
     *
199
     * @param ServerRequestInterface $request
200
     *
201
     * @return RouteInterface
202
     *
203
     * @throws MethodNotAllowedException
204
     * @throws RouteNotFoundException
205
     */
206 11
    public function match(ServerRequestInterface $request) : RouteInterface
207
    {
208 11
        $routes = [];
209 11
        foreach ($this->routes as $route) {
210 11
            foreach ($route->getMethods() as $method) {
211 11
                $routes[$method][] = $route;
212
            }
213
        }
214
215 11
        $method = $request->getMethod();
216 11
        if (!isset($routes[$method])) {
217 3
            $errmsg = sprintf('The method "%s" is not allowed.', $method);
218
219 3
            throw new MethodNotAllowedException($errmsg, [
220 3
                'allowed' => array_keys($routes),
221
            ]);
222
        }
223
224 8
        $target = $request->getUri()->getPath();
225 8
        foreach ($routes[$method] as $route) {
226 8
            if (path_match($route->getPath(), $target, $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

226
            if (/** @scrutinizer ignore-call */ path_match($route->getPath(), $target, $attributes)) {
Loading history...
227 8
                return $route->withAddedAttributes($attributes);
228
            }
229
        }
230
231 3
        throw new RouteNotFoundException(
232 3
            sprintf('No route found for the URI "%s".', $target)
233
        );
234
    }
235
236
    /**
237
     * {@inheritDoc}
238
     */
239 8
    public function handle(ServerRequestInterface $request) : ResponseInterface
240
    {
241 8
        $route = $this->match($request);
242
243 4
        $handler = new QueueableRequestHandler($route);
244 4
        $handler->add(...$this->getMiddlewares());
245
246 4
        return $handler->handle($request);
247
    }
248
249
    /**
250
     * {@inheritDoc}
251
     */
252 3
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
253
    {
254
        try {
255 3
            return $this->handle($request);
256 2
        } catch (MethodNotAllowedException | RouteNotFoundException $e) {
257 2
            return $handler->handle(
258 2
                $request->withAttribute(self::ATTR_NAME_FOR_ROUTING_ERROR, $e)
259
            );
260
        }
261
    }
262
263
    /**
264
     * Loads routes through the given loaders
265
     *
266
     * @param LoaderInterface ...$loaders
267
     *
268
     * @return void
269
     */
270 2
    public function load(LoaderInterface ...$loaders) : void
271
    {
272 2
        foreach ($loaders as $loader) {
273 2
            $this->addRoute(...$loader->load()->all());
274
        }
275 2
    }
276
}
277