Completed
Push — master ( 29f7ac...56fa27 )
by Anton
01:47
created

Router::loadDataFromLoader()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

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