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

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

223
            if (/** @scrutinizer ignore-call */ path_match($route->getPath(), $target, $attributes)) {
Loading history...
Comprehensibility Best Practice introduced by
The variable $attributes seems to be never defined.
Loading history...
224 8
                return $route->withAddedAttributes($attributes);
225
            }
226
        }
227
228 3
        throw new RouteNotFoundException(
229 3
            sprintf('No route found for the URI "%s".', $target)
230
        );
231
    }
232
233
    /**
234
     * {@inheritDoc}
235
     */
236 8
    public function handle(ServerRequestInterface $request) : ResponseInterface
237
    {
238 8
        $route = $this->match($request);
239
240 4
        $handler = new QueueableRequestHandler($route);
241 4
        $handler->add(...$this->getMiddlewares());
242
243 4
        return $handler->handle($request);
244
    }
245
246
    /**
247
     * {@inheritDoc}
248
     */
249 3
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
250
    {
251
        try {
252 3
            return $this->handle($request);
253 2
        } catch (ExceptionInterface $e) {
254 2
            return $handler->handle(
255 2
                $request->withAttribute(self::ATTR_NAME_FOR_ROUTING_ERROR, $e)
256
            );
257
        }
258
    }
259
260
    /**
261
     * Route grouping logic
262
     *
263
     * @param callable $callback
264
     *
265
     * @return RouteCollectionGroupActionInterface
266
     */
267 1
    public function group(callable $callback) : RouteCollectionGroupActionInterface
268
    {
269 1
        $collector = new RouteCollector();
270
271 1
        $callback($collector);
272
273 1
        $this->addRoute(...$collector->getCollection()->all());
274
275 1
        return new RouteCollectionGroupAction($collector->getCollection());
276
    }
277
}
278