Router   A
last analyzed

Complexity

Total Complexity 38

Size/Duplication

Total Lines 357
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
wmc 38
eloc 102
c 6
b 0
f 0
dl 0
loc 357
ccs 114
cts 114
cp 1
rs 9.36

21 Methods

Rating   Name   Duplication   Size   Complexity  
A map() 0 6 1
A group() 0 6 1
A addGroup() 0 3 1
A setMiddlewares() 0 3 1
A __construct() 0 23 4
A addRoute() 0 4 1
A createRoute() 0 6 2
A createDispatcherAndReverser() 0 4 1
A reverse() 0 3 1
A getReverser() 0 4 1
A process() 0 22 4
A fetchDataFromCache() 0 7 3
A loadData() 0 36 6
A url() 0 3 1
A loadDataFromLoader() 0 4 2
A getDispatcher() 0 4 1
A getCollector() 0 3 1
A dispatch() 0 3 1
A getInvoker() 0 6 2
A cacheData() 0 7 2
A setIsDirty() 0 3 1
1
<?php declare(strict_types=1);
2
3
namespace Phact\Router;
4
5
use FastRoute\Dispatcher;
6
use Phact\Router\Dispatcher\GroupCountBasedDispatcherFactory;
7
use Phact\Router\Exception\HttpException;
8
use Phact\Router\Exception\MethodNotAllowedException;
9
use Phact\Router\Invoker\InvokerAwareInterface;
10
use Phact\Router\Invoker\InvokerAwareTrait;
11
use Phact\Router\Loader\LoaderAwareInterface;
12
use Phact\Router\Loader\LoaderAwareTrait;
13
use Phact\Router\Reverser\StdReverserFactory;
14
use Phact\Router\ReverserDataGenerator\Std;
15
use Psr\Http\Message\ResponseInterface;
16
use Psr\Http\Message\ServerRequestInterface;
17
use Psr\Http\Server\MiddlewareInterface;
18
use Psr\Http\Server\RequestHandlerInterface;
19
20
class Router implements
21
    MiddlewareInterface,
22
    RouteCollector,
23
    LoaderAwareInterface,
24
    CacheAwareInterface,
25
    InvokerAwareInterface,
26
    ContainerAwareInterface
27
{
28
    use LoaderAwareTrait;
29
    use CacheAwareTrait;
30
    use ContainerAwareTrait;
31
    use InvokerAwareTrait;
32
33
    /**
34
     * @var Collector
35
     */
36
    protected $collector;
37
38
    /**
39
     * @var DispatcherFactory
40
     */
41
    protected $dispatcherFabric;
42
43
    /**
44
     * @var ReverserFactory
45
     */
46
    protected $reverserFabric;
47
48
    /**
49
     * @var Dispatcher
50
     */
51
    protected $dispatcher;
52
53
    /**
54
     * @var Reverser
55
     */
56
    protected $reverser;
57
58
    /**
59
     * Is new generation of reverser and dispatcher needed
60
     *
61
     * @var bool
62
     */
63
    protected $isDirty = true;
64
65
    /**
66
     * Is loaded data from Loader or cache
67
     *
68
     * @var bool
69
     */
70
    protected $isLoaded = false;
71
72
    /**
73
     * @var array
74
     */
75
    protected $currentMiddlewares = [];
76
77 31
    public function __construct(
78
        ?Collector $collector = null,
79
        ?DispatcherFactory $dispatcherFabric = null,
80
        ?ReverserFactory $reverserFabric = null
81
    ) {
82 31
        if ($collector === null) {
83 16
            $collector = new Collector(
84 16
                new \FastRoute\RouteParser\Std(),
85 16
                new \FastRoute\DataGenerator\GroupCountBased(),
86 16
                new Std()
87
            );
88
        }
89 31
        $this->collector = $collector;
90
91 31
        if ($dispatcherFabric === null) {
92 25
            $dispatcherFabric = new GroupCountBasedDispatcherFactory();
93
        }
94 31
        $this->dispatcherFabric = $dispatcherFabric;
95
96 31
        if ($reverserFabric === null) {
97 29
            $reverserFabric = new StdReverserFactory();
98
        }
99 31
        $this->reverserFabric = $reverserFabric;
100 31
    }
101
102
    /**
103
     * Set common middlewares
104
     *
105
     * @param array $middlewares
106
     */
107 1
    public function setMiddlewares(array $middlewares): void
108
    {
109 1
        $this->currentMiddlewares = $middlewares;
110 1
    }
111
112
    /**
113
     * @inheritDoc
114
     */
115 6
    public function addRoute($httpMethod, $route, $handler, ?string $name = null): void
116
    {
117 6
        $this->collector->map($httpMethod, $route, $handler, $name);
118 6
        $this->setIsDirty();
119 6
    }
120
121
    /**
122
     * @inheritDoc
123
     */
124 3
    public function addGroup($prefix, callable $callback, ?string $name = null): void
125
    {
126 3
        $this->collector->group($prefix, $callback, $name, $this);
127 3
    }
128
129
    /**
130
     * @inheritDoc
131
     */
132 10
    public function map($httpMethod, $route, $handler, ?string $name = null, array $middlewares = []): void
133
    {
134 10
        $middlewares = array_merge($this->currentMiddlewares, $middlewares);
135 10
        $routeHandler = $this->createRoute($handler, $this->collector->getCurrentGroupName() . $name, $middlewares);
136 10
        $this->collector->map($httpMethod, $route, $routeHandler, $name);
137 10
        $this->setIsDirty();
138 10
    }
139
140
    /**
141
     * @inheritDoc
142
     */
143 4
    public function group($prefix, callable $callback, ?string $name = null, array $middlewares = []): void
144
    {
145 4
        $previousMiddlewares = $this->currentMiddlewares;
146 4
        $this->currentMiddlewares = array_merge($previousMiddlewares, $middlewares);
147 4
        $this->collector->group($prefix, $callback, $name, $this);
148 4
        $this->currentMiddlewares = $previousMiddlewares;
149 4
    }
150
151
    /**
152
     * Create handler wrapper
153
     *
154
     * @param mixed $handler
155
     * @param MiddlewareInterface[] $middlewares
156
     * @param string|null $name
157
     * @return RouterHandler
158
     */
159 10
    protected function createRoute($handler, ?string $name = null, array $middlewares = []): RouterHandler
160
    {
161 10
        if ($handler instanceof RouterHandler) {
162 1
            return $handler;
163
        }
164 9
        return new Route($handler, $name, $middlewares);
165
    }
166
167
    /**
168
     * Get Collector
169
     *
170
     * @return Collector
171
     */
172 22
    public function getCollector(): Collector
173
    {
174 22
        return $this->collector;
175
    }
176
177
    /**
178
     * Get Invoker
179
     *
180
     * @return Invoker
181
     */
182 4
    public function getInvoker(): Invoker
183
    {
184 4
        if (!$this->invoker) {
185 3
            $this->invoker = new \Phact\Router\Invoker\Std($this->container);
186
        }
187 4
        return $this->invoker;
188
    }
189
190
    /**
191
     * Mark as dirty, data re-load needed
192
     */
193 16
    protected function setIsDirty(): void
194
    {
195 16
        $this->isDirty = true;
196 16
    }
197
198
    /**
199
     * Load data from cache or loader
200
     */
201 24
    protected function loadData(): void
202
    {
203 24
        $dispatcherData = [[], []];
204 24
        $reverserData = [];
205 24
        $cacheRequired = true;
206
207
        // If data exists in cache, load it from cache
208 24
        if (!$this->isLoaded && ($data = $this->fetchDataFromCache())) {
209 2
            [$dispatcherData, $reverserData] = $data;
210 2
            $this->isLoaded = true;
211 2
            $cacheRequired = false;
212
        }
213
214
        // If not loaded
215 24
        if (!$this->isLoaded) {
216
            // If loader exists, load data with loader
217 22
            $this->loadDataFromLoader();
218
            // Extract data from collector
219 22
            $dispatcherData = $this->getCollector()->getData();
220 22
            $reverserData = $this->getCollector()->getReverserData();
221 22
            $this->isLoaded = true;
222
        }
223
224
        // If no changes, just pass
225 24
        if (!$this->isDirty) {
226 2
            return;
227
        }
228
229
        // Update cache if needed
230 24
        if ($cacheRequired) {
231 22
            $this->cacheData($dispatcherData, $reverserData);
232
        }
233
234 24
        $this->createDispatcherAndReverser($dispatcherData, $reverserData);
235
236 24
        $this->isDirty = false;
237 24
    }
238
239
    /**
240
     * Load data from loader
241
     */
242 22
    protected function loadDataFromLoader(): void
243
    {
244 22
        if ($this->loader) {
245 2
            $this->loader->load($this);
246
        }
247 22
    }
248
249
    /**
250
     * Load data from loader
251
     */
252 24
    protected function fetchDataFromCache(): ?array
253
    {
254 24
        if ($this->cache && $this->cache->has($this->cacheKey)) {
255
            // Extract data from cache
256 2
            return $this->cache->get($this->cacheKey);
257
        }
258 22
        return null;
259
    }
260
261
    /**
262
     * Add data to cache
263
     *
264
     * @param $dispatcherData
265
     * @param $reverserData
266
     */
267 22
    protected function cacheData($dispatcherData, $reverserData): void
268
    {
269 22
        if ($this->cache) {
270 2
            $this->cache->set($this->cacheKey, [
271 2
                $dispatcherData,
272 2
                $reverserData
273 2
            ], $this->cacheTTL);
274
        }
275 22
    }
276
277
    /**
278
     * Create reverser and dispatcher with provided data
279
     *
280
     * @param $dispatcherData
281
     * @param $reverserData
282
     */
283 24
    protected function createDispatcherAndReverser($dispatcherData, $reverserData): void
284
    {
285 24
        $this->dispatcher = $this->dispatcherFabric->createDispatcher($dispatcherData);
286 24
        $this->reverser = $this->reverserFabric->createReverser($reverserData);
287 24
    }
288
289
    /**
290
     * @return Reverser
291
     */
292 6
    public function getReverser(): Reverser
293
    {
294 6
        $this->loadData();
295 6
        return $this->reverser;
296
    }
297
298
    /**
299
     * @return Dispatcher
300
     */
301 18
    public function getDispatcher(): Dispatcher
302
    {
303 18
        $this->loadData();
304 18
        return $this->dispatcher;
305
    }
306
307
    /**
308
     * Generate URL by name and params
309
     *
310
     * @param string $routeName
311
     * @param array $variables
312
     * @return string
313
     */
314 6
    public function reverse(string $routeName, array $variables = []): string
315
    {
316 6
        return $this->getReverser()->reverse($routeName, $variables);
317
    }
318
319
    /**
320
     * Generate URL by name and params (alias for reverse())
321
     *
322
     * @param string $routeName
323
     * @param array $variables
324
     * @return string
325
     */
326 1
    public function url(string $routeName, array $variables = []): string
327
    {
328 1
        return $this->reverse($routeName, $variables);
329
    }
330
331
    /**
332
     * Dispatches against the provided HTTP method verb and URI.
333
     *
334
     * Returns array with one of the following formats:
335
     *
336
     *     [Dispatcher::NOT_FOUND]
337
     *     [Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
338
     *     [Dispatcher::FOUND, $handler, ['varName' => 'value', ...]]
339
     *
340
     * @param string $httpMethod
341
     * @param string $uri
342
     *
343
     * @return array
344
     */
345 18
    public function dispatch(string $httpMethod, string $uri): array
346
    {
347 18
        return $this->getDispatcher()->dispatch($httpMethod, $uri);
348
    }
349
350
    /**
351
     * @inheritDoc
352
     *
353
     * @throws HttpException
354
     */
355 8
    public function process(
356
        ServerRequestInterface $request,
357
        RequestHandlerInterface $notFoundHandler
358
    ): ResponseInterface {
359 8
        $httpMethod = $request->getMethod();
360 8
        $uri = $request->getUri()->getPath();
361
362 8
        $match = $this->dispatch($httpMethod, $uri);
363
364 8
        switch ($match[0]) {
365 8
            case Dispatcher::NOT_FOUND:
366 2
                break;
367 6
            case Dispatcher::METHOD_NOT_ALLOWED:
368 1
                $allowed = (array)$match[1];
369 1
                throw new MethodNotAllowedException($allowed);
370
                break;
371 5
            case Dispatcher::FOUND:
372 4
                $params = $match[2];
373 4
                return $this->getInvoker()->invoke($request, $match[1], $params);
374
        }
375
376 3
        return $notFoundHandler->handle($request);
377
    }
378
}
379