FastRouteAdaptor::_getCachedDispatcher()   A
last analyzed

Complexity

Conditions 2
Paths 1

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 0
cts 12
cp 0
rs 9.3142
c 0
b 0
f 0
cc 2
eloc 11
nc 1
nop 2
crap 6
1
<?php
2
/**
3
 * This file is part of the DS Framework.
4
 *
5
 * (c) Dan Smith <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace Ds\Router\Adaptor;
11
12
use FastRoute\Dispatcher;
13
use FastRoute\RouteCollector;
14
use Ds\Router\Exceptions\AdaptorException;
15
use Ds\Router\Exceptions\RouterException;
16
use Ds\Router\Interfaces\RouteCollectionInterface;
17
use Ds\Router\Interfaces\RouteInterface;
18
use Ds\Router\Interfaces\SerializerInterface;
19
use Ds\Router\RouteCollection;
20
use Ds\Router\RouterResponse;
21
22
/**
23
 * Class FastRouteAdaptor
24
 *
25
 * @package Ds\Router\Adaptor
26
 * @author  Dan Smith    <[email protected]>
27
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
28
 */
29
class FastRouteAdaptor extends AbstractAdaptor
30
{
31
    /**
32
     * FastRouteAdaptor constructor.
33
     *
34
     * @param SerializerInterface $serializer Serializer for Cached Routes.
35
     * @param array               $options    Fast Route options.
36
     *
37
     * @throws RouterException
38
     */
39 11
    public function __construct(SerializerInterface $serializer, array $options = [])
40
    {
41 11
        parent::__construct($serializer);
42 11
        $this->options = $options;
43
44 11
        if (!isset($this->options['errorHandlers']['default'])) {
45 1
            throw new RouterException('Error Handlers must be specified');
46
        }
47
48 11
        $this->_checkCacheExpires();
49 11
    }
50
51
    /**
52
     * Check that cache file hasn't expired.
53
     * @return bool
54
     * @throws RouterException
55
     */
56 11
    protected function _checkCacheExpires()
57
    {
58 11
        if (isset($this->options['cacheExpires'], $this->options['cacheFile'])
59 11
            && $this->isCached()
60
        ) {
61
            $timeCacheCreated = \filemtime($this->options['cacheFile']);
62
            $timeNow = \time();
63
            $timeExpires = $timeCacheCreated + $this->options['cacheExpires'];
64
            if ($timeExpires - $timeNow < 0) {
65
                $removed = \unlink($this->options['cacheFile']);
66
                if (!$removed) {
67
                    throw new RouterException('Unable to delete file: ' . $this->options['cacheFile']);
68
                }
69
            }
70
        }
71 11
        return true;
72
    }
73
74
    /**
75
     * @inheritdoc
76
     */
77 1
    public function isCached()
78
    {
79 1
        $cacheDir = $this->options['cacheFile'] ?? __DIR__;
80
81 1
        $disabledCache = isset($this->options['cacheDisabled'])
82 1
            ? (bool)$this->options['cacheDisabled'] : true;
83
84 1
        if ($disabledCache === false) {
85
            return \file_exists($cacheDir) ? true : false;
86
        }
87
88 1
        return false;
89
    }
90
91
    /**
92
     * @inheritdoc
93
     */
94
    public function match(RouteCollectionInterface $routes, $method, $requestTarget)
95
    {
96
        $fastRouteResponse = [
97
            'statusCode' => 500,
98
            'allowedMethods' => [],
99
            'vars' => []
100
        ];
101
102
        $foundRoute = $this->_getCachedDispatcher($routes)->dispatch($method, $requestTarget);
103
104
        switch ($foundRoute[0]) {
105
            case Dispatcher::FOUND:
0 ignored issues
show
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
106
                $fastRouteResponse['statusCode'] = 200;
107
                $fastRouteResponse['vars'] = $foundRoute[2];
108
                $fastRouteResponse['handler'] = $foundRoute[1];
109
                break;
110 View Code Duplication
            case Dispatcher::NOT_FOUND:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Coding Style introduced by
case statements should be defined using a colon.

As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next break.

There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.

switch ($expr) {
    case "A": { //wrong
        doSomething();
        break;
    }
    case "B"; //wrong
        doSomething();
        break;
    case "C": //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
111
                $fastRouteResponse['statusCode'] = 404;
112
                $fastRouteResponse['handler'] = $this->_findOptionsHandler(404);
113
                break;
114 View Code Duplication
            case Dispatcher::METHOD_NOT_ALLOWED:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
115
                $fastRouteResponse['statusCode'] = 405;
116
                $fastRouteResponse['handler'] = $this->_findOptionsHandler(405);
117
                break;
118
            default:
119
                $fastRouteResponse['handler'] = $this->_findOptionsHandler('default');
120
        }
121
        return $this->_createRouterResponse($fastRouteResponse);
122
    }
123
124
    /**
125
     * Get Fast Route Cached Dispatcher.
126
     *
127
     * @param RouteCollectionInterface $routes
128
     * @param array $options
129
     * @return Dispatcher
130
     */
131
    protected function _getCachedDispatcher(RouteCollectionInterface $routes, array $options = array())
132
    {
133
        $options += $this->options;
134
        $this->options = $options;
135
136
        return \FastRoute\cachedDispatcher(
137
            function (RouteCollector $fastRouteCollector) use ($routes) {
138
                /**
139
                 * @var $route RouteInterface
140
                 */
141
                foreach ($routes as $route) {
142
                    $fastRouteCollector->addRoute(
143
                        $route->getMethod(),
144
                        $route->getPattern(),
145
                        $this->_createFastRouteHandler($route)
146
                    );
147
                }
148
            },
149
            $this->options
150
        );
151
    }
152
153
    /**
154
     * @param RouteInterface $route
155
     * @return array
156
     */
157 View Code Duplication
    protected function _createFastRouteHandler(RouteInterface $route)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
158
    {
159
        $handlerOut = [
160
            'type' => $route->getHandlerType(),
161
            'name' => $route->getNames()
162
        ];
163
164
        if ($route->getHandlerType() === 'object') {
165
            $handlerOut['content'] = $this->serializer->serialize($route->getHandler());
0 ignored issues
show
Bug introduced by
It seems like $route->getHandler() targeting Ds\Router\Interfaces\RouteInterface::getHandler() can also be of type string; however, Ds\Router\Interfaces\Ser...rInterface::serialize() does only seem to accept object<Closure>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
166
        } else {
167
            $handlerOut['content'] = $route->getHandler();
168
        }
169
170
        return $handlerOut;
171
    }
172
173
    /**
174
     * Return handler if it exists.
175
     * @param $code
176
     * @return mixed
177
     */
178
    protected function _findOptionsHandler($code)
179
    {
180
        $handlerArgs = $this->options['errorHandlers']['default'];
181
182
        if (isset($this->options['errorHandlers'][$code])) {
183
            $handlerArgs = $this->options['errorHandlers'][$code];
184
        }
185
186
        return [
187
            'type' => 'string',
188
            'content' => $handlerArgs['handler'],
189
            'name' => $handlerArgs['name']
190
        ];
191
    }
192
193
    /**
194
     * Unserialize any cached content and return RouterResponse.
195
     *
196
     * @param $dispatchResponse
197
     * @return RouterResponse
198
     */
199 View Code Duplication
    protected function _createRouterResponse($dispatchResponse)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
    {
201
        if ($dispatchResponse['handler']['type'] === 'object') {
202
            $dispatchResponse['content'] = $this->serializer->unserialize($dispatchResponse['handler']['content']);
203
        } else {
204
            $dispatchResponse['content'] = $dispatchResponse['handler']['content'];
205
        }
206
207
        return new RouterResponse(
208
            $dispatchResponse['statusCode'],
209
            $dispatchResponse['content'],
210
            $dispatchResponse['handler']['name'],
211
            $dispatchResponse['vars']
212
        );
213
    }
214
215
    /**
216
     * @inheritdoc
217
     *
218
     * @throws \Rs\Router\Exceptions\UniqueRouteException
219
     */
220
    public function getCachedRoutes($context = '')
221
    {
222
        $collection = new RouteCollection();
223
224
        if ($this->isCached()) {
225
226
            $cacheFile = $this->getOption('cacheFile');
227
228
            if (!\file_exists($cacheFile)) {
229
                throw new AdaptorException('Cache file: ' . $cacheFile . ' not found');
230
            }
231
232
            $routes = (array)require $cacheFile;
233
234
            foreach ($routes[0] as $routeMethod => $routeData) {
235
                foreach ($routeData as $routePattern => $routeContent){
236
                    $routePath = \rtrim($context, '/') . DIRECTORY_SEPARATOR . \ltrim($routePattern, '/');
237
                    $collection->addRoute($routeMethod, $routePath, $routeContent['content'], $routeContent['name']);
238
                }
239
            }
240
        }
241
        return $collection;
242
    }
243
}
244