Completed
Push — master ( 56fa27...1bbc39 )
by Anton
01:49
created

Router::loadData()   A

Complexity

Conditions 6
Paths 12

Size

Total Lines 36
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 18
c 2
b 0
f 0
dl 0
loc 36
ccs 19
cts 19
cp 1
rs 9.0444
cc 6
nc 12
nop 0
crap 6
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
     * @return Collector
169
     */
170 22
    public function getCollector(): Collector
171
    {
172 22
        return $this->collector;
173
    }
174
175
    /**
176
     * @return Invoker
177
     */
178 4
    public function getInvoker(): Invoker
179
    {
180 4
        if (!$this->invoker) {
181 3
            $this->invoker = new \Phact\Router\Invoker\Std($this->container);
182
        }
183 4
        return $this->invoker;
184
    }
185
186
    /**
187
     * Mark as dirty, data re-load needed
188
     */
189 16
    protected function setIsDirty(): void
190
    {
191 16
        $this->isDirty = true;
192 16
    }
193
194
    /**
195
     * Load data from cache or loader
196
     */
197 24
    protected function loadData(): void
198
    {
199 24
        $dispatcherData = [[], []];
200 24
        $reverserData = [];
201 24
        $cacheRequired = true;
202
203
        // If data exists in cache, load it from cache
204 24
        if (!$this->isLoaded && ($data = $this->fetchDataFromCache())) {
205 2
            [$dispatcherData, $reverserData] = $data;
206 2
            $this->isLoaded = true;
207 2
            $cacheRequired = false;
208
        }
209
210
        // If not loaded
211 24
        if (!$this->isLoaded) {
212
            // If loader exists, load data with loader
213 22
            $this->loadDataFromLoader();
214
            // Extract data from collector
215 22
            $dispatcherData = $this->getCollector()->getData();
216 22
            $reverserData = $this->getCollector()->getReverserData();
217 22
            $this->isLoaded = true;
218
        }
219
220
        // If no changes, just pass
221 24
        if (!$this->isDirty) {
222 2
            return;
223
        }
224
225
        // Update cache if needed
226 24
        if ($cacheRequired) {
227 22
            $this->cacheData($dispatcherData, $reverserData);
228
        }
229
230 24
        $this->createDispatcherAndReverser($dispatcherData, $reverserData);
231
232 24
        $this->isDirty = false;
233 24
    }
234
235
    /**
236
     * Load data from loader
237
     */
238 22
    protected function loadDataFromLoader(): void
239
    {
240 22
        if ($this->loader) {
241 2
            $this->loader->load($this);
242
        }
243 22
    }
244
245
    /**
246
     * Load data from loader
247
     */
248 24
    protected function fetchDataFromCache(): ?array
249
    {
250 24
        if ($this->cache && $this->cache->has($this->cacheKey)) {
251
            // Extract data from cache
252 2
            return $this->cache->get($this->cacheKey);
253
        }
254 22
        return null;
255
    }
256
257
    /**
258
     * Add data to cache
259
     *
260
     * @param $dispatcherData
261
     * @param $reverserData
262
     */
263 22
    protected function cacheData($dispatcherData, $reverserData): void
264
    {
265 22
        if ($this->cache) {
266 2
            $this->cache->set($this->cacheKey, [
267 2
                $dispatcherData,
268 2
                $reverserData
269 2
            ], $this->cacheTTL);
270
        }
271 22
    }
272
273
    /**
274
     * Create reverser and dispatcher with provided data
275
     *
276
     * @param $dispatcherData
277
     * @param $reverserData
278
     */
279 24
    protected function createDispatcherAndReverser($dispatcherData, $reverserData): void
280
    {
281 24
        $this->dispatcher = $this->dispatcherFabric->createDispatcher($dispatcherData);
282 24
        $this->reverser = $this->reverserFabric->createReverser($reverserData);
283 24
    }
284
285
    /**
286
     * @return Reverser
287
     */
288 6
    public function getReverser(): Reverser
289
    {
290 6
        $this->loadData();
291 6
        return $this->reverser;
292
    }
293
294
    /**
295
     * @return Dispatcher
296
     */
297 18
    public function getDispatcher(): Dispatcher
298
    {
299 18
        $this->loadData();
300 18
        return $this->dispatcher;
301
    }
302
303
    /**
304
     * @param string $routeName
305
     * @param array $variables
306
     * @return string
307
     */
308 6
    public function reverse(string $routeName, array $variables = []): string
309
    {
310 6
        return $this->getReverser()->reverse($routeName, $variables);
311
    }
312
313
    /**
314
     * Alias for reverse
315
     *
316
     * @param string $routeName
317
     * @param array $variables
318
     * @return string
319
     */
320 1
    public function url(string $routeName, array $variables = []): string
321
    {
322 1
        return $this->reverse($routeName, $variables);
323
    }
324
325
    /**
326
     * Dispatches against the provided HTTP method verb and URI.
327
     *
328
     * Returns array with one of the following formats:
329
     *
330
     *     [Dispatcher::NOT_FOUND]
331
     *     [Dispatcher::METHOD_NOT_ALLOWED, ['GET', 'OTHER_ALLOWED_METHODS']]
332
     *     [Dispatcher::FOUND, $handler, ['varName' => 'value', ...]]
333
     *
334
     * @param string $httpMethod
335
     * @param string $uri
336
     *
337
     * @return array
338
     */
339 18
    public function dispatch(string $httpMethod, string $uri): array
340
    {
341 18
        return $this->getDispatcher()->dispatch($httpMethod, $uri);
342
    }
343
344
    /**
345
     * @inheritDoc
346
     *
347
     * @throws HttpException
348
     */
349 8
    public function process(
350
        ServerRequestInterface $request,
351
        RequestHandlerInterface $notFoundHandler
352
    ): ResponseInterface {
353 8
        $httpMethod = $request->getMethod();
354 8
        $uri = $request->getUri()->getPath();
355
356 8
        $match = $this->dispatch($httpMethod, $uri);
357
358 8
        switch ($match[0]) {
359 8
            case Dispatcher::NOT_FOUND:
360 2
                break;
361 6
            case Dispatcher::METHOD_NOT_ALLOWED:
362 1
                $allowed = (array)$match[1];
363 1
                throw new MethodNotAllowedException($allowed);
364
                break;
365 5
            case Dispatcher::FOUND:
366 4
                $params = $match[2];
367 4
                return $this->getInvoker()->invoke($request, $match[1], $params);
368
        }
369
370 3
        return $notFoundHandler->handle($request);
371
    }
372
}
373