Router   A
last analyzed

Complexity

Total Complexity 19

Size/Duplication

Total Lines 210
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 19
eloc 52
c 2
b 0
f 0
dl 0
loc 210
rs 10

15 Methods

Rating   Name   Duplication   Size   Complexity  
A post() 0 3 1
A run() 0 12 2
A createMiddlewareWithParams() 0 9 3
A normalizeMethod() 0 3 1
A options() 0 3 1
A add() 0 5 1
A patch() 0 3 1
A pushRule() 0 4 1
A get() 0 3 1
A put() 0 3 1
A handle() 0 13 1
A head() 0 3 1
A __construct() 0 10 1
A delete() 0 3 1
A all() 0 4 2
1
<?php
2
3
namespace Tleckie\Router;
4
5
use Closure;
6
use HttpSoft\Emitter\EmitterInterface;
7
use Psr\Http\Message\ResponseFactoryInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\ServerRequestFactoryInterface;
10
use Psr\Http\Server\MiddlewareInterface;
11
use Tleckie\Router\Exception\RouteNotFoundException;
12
13
/**
14
 * Class Router
15
 *
16
 * @package Tleckie\Router
17
 * @author  Teodoro Leckie Westberg <[email protected]>
18
 */
19
class Router
20
{
21
    /** @var array */
22
    private const METHODS = [
23
        'GET',
24
        'POST',
25
        'HEAD',
26
        'PATCH',
27
        'OPTIONS',
28
        'DELETE',
29
        'PUT',
30
    ];
31
32
    /** @var array[] */
33
    private array $items;
34
35
    /** @var ServerRequestFactoryInterface */
36
    private ServerRequestFactoryInterface $requestFactory;
37
38
    /** @var ResponseFactoryInterface */
39
    private ResponseFactoryInterface $responseFactory;
40
41
    /** @var MiddlewareFactory */
42
    private MiddlewareFactory $middlewareFactory;
43
44
    /** @var EmitterInterface */
45
    private EmitterInterface $emitter;
46
47
    /** @var MiddlewareInterface[] */
48
    private array $middlewares;
49
50
    /**
51
     * Router constructor.
52
     *
53
     * @param ServerRequestFactoryInterface $requestFactory
54
     * @param ResponseFactoryInterface      $responseFactory
55
     * @param EmitterInterface              $emitter
56
     */
57
    public function __construct(
58
        ServerRequestFactoryInterface $requestFactory,
59
        ResponseFactoryInterface $responseFactory,
60
        EmitterInterface $emitter
61
    ) {
62
        $this->requestFactory = $requestFactory;
63
        $this->responseFactory = $responseFactory;
64
        $this->emitter = $emitter;
65
        $this->middlewares = [];
66
        $this->middlewareFactory = new MiddlewareFactory();
67
    }
68
69
    /**
70
     * @param callable|MiddlewareInterface|Closure $middleware
71
     * @return $this
72
     */
73
    public function add(callable|MiddlewareInterface|Closure $middleware): self
74
    {
75
        $this->middlewares[] = $middleware;
76
77
        return $this;
78
    }
79
80
    /**
81
     * @param string   $path
82
     * @param callable ...$callback
83
     */
84
    public function get(string $path, callable ...$callback): void
85
    {
86
        $this->pushRule('GET', $path, ...$callback);
87
    }
88
89
    /**
90
     * @param string   $method
91
     * @param string   $path
92
     * @param callable ...$callback
93
     */
94
    private function pushRule(string $method, string $path, callable ...$callback): void
95
    {
96
        $method = $this->normalizeMethod($method);
97
        $this->items[$method][$path] = $callback;
98
    }
99
100
    /**
101
     * @param string $method
102
     * @return string
103
     */
104
    private function normalizeMethod(string $method): string
105
    {
106
        return strtoupper($method);
107
    }
108
109
    /**
110
     * @param string   $path
111
     * @param callable ...$callback
112
     */
113
    public function all(string $path, callable ...$callback): void
114
    {
115
        foreach (static::METHODS as $method) {
116
            $this->pushRule($method, $path, ...$callback);
117
        }
118
    }
119
120
    /**
121
     * @param string   $path
122
     * @param callable ...$callback
123
     */
124
    public function post(string $path, callable ...$callback): void
125
    {
126
        $this->pushRule('POST', $path, ...$callback);
127
    }
128
129
    /**
130
     * @param string   $path
131
     * @param callable ...$callback
132
     */
133
    public function head(string $path, callable ...$callback): void
134
    {
135
        $this->pushRule('HEAD', $path, ...$callback);
136
    }
137
138
    /**
139
     * @param string   $path
140
     * @param callable ...$callback
141
     */
142
    public function patch(string $path, callable ...$callback): void
143
    {
144
        $this->pushRule('PATCH', $path, ...$callback);
145
    }
146
147
    /**
148
     * @param string   $path
149
     * @param callable ...$callback
150
     */
151
    public function options(string $path, callable ...$callback): void
152
    {
153
        $this->pushRule('OPTIONS', $path, ...$callback);
154
    }
155
156
    /**
157
     * @param string   $path
158
     * @param callable ...$callback
159
     */
160
    public function delete(string $path, callable ...$callback): void
161
    {
162
        $this->pushRule('DELETE', $path, ...$callback);
163
    }
164
165
    /**
166
     * @param string   $path
167
     * @param callable ...$callback
168
     */
169
    public function put(string $path, callable ...$callback): void
170
    {
171
        $this->pushRule('PUT', $path, ...$callback);
172
    }
173
174
    /**
175
     * @param string $method
176
     * @param        $uri
177
     * @param array  $serverParams
178
     * @throws RouteNotFoundException
179
     */
180
    public function run(string $method, $uri, array $serverParams = []): void
181
    {
182
        $method = $this->normalizeMethod($method);
183
        $response = $this->responseFactory->createResponse();
184
        $request = $this->requestFactory->createServerRequest($method, $uri, $serverParams);
185
        $response = $this->handle($request, $response, $method);
186
187
        if ('HEAD' === $method) {
188
            $response = $response->withBody($this->responseFactory->createResponse()->getBody());
189
        }
190
191
        $this->emitter->emit($response);
192
    }
193
194
    /**
195
     * @param $request
196
     * @param $response
197
     * @param $method
198
     * @return ResponseInterface
199
     * @throws RouteNotFoundException
200
     */
201
    private function handle($request, $response, $method): ResponseInterface
202
    {
203
        $findRoute = new FindRoutes($this->middlewareFactory, $this->items);
204
205
        $item = $findRoute->find($method, $request->getUri()->getPath());
206
207
        $middlewareDispatcher = new MiddlewareDispatcher(
208
            $response,
209
            ...array_reverse($item->middleware()),
210
            ...array_reverse($this->createMiddlewareWithParams($item->params()))
211
        );
212
213
        return $item->handle($request, $middlewareDispatcher->handle($request));
214
    }
215
216
    /**
217
     * @param array $params
218
     * @return MiddlewareInterface[]
219
     */
220
    private function createMiddlewareWithParams(array $params = []): array
221
    {
222
        foreach ($this->middlewares as $index => $middleware) {
223
            if (!$middleware instanceof MiddlewareInterface) {
224
                $this->middlewares[$index] = $this->middlewareFactory->create($middleware, $params);
225
            }
226
        }
227
228
        return $this->middlewares;
229
    }
230
}
231