Test Failed
Push — master ( 0ee32e...41c938 )
by Dan
07:10
created

FastRouteAdaptor::getCachedRoutes()   B

Complexity

Conditions 6
Paths 3

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.5125
c 0
b 0
f 0
cc 6
eloc 13
nc 3
nop 1
1
<?php
2
/**
3
 * This file is part of the PSR Http 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
 * @link    https://github.com/djsmithme/Router
29
 */
30
class FastRouteAdaptor extends AbstractAdaptor
31
{
32
    /**
33
     * FastRouteAdaptor constructor.
34
     *
35
     * @param SerializerInterface $serializer Serializer for Cached Routes.
36
     * @param array               $options    Fast Route options.
37
     *
38
     * @throws RouterException
39
     */
40
    public function __construct(SerializerInterface $serializer, array $options = [])
41
    {
42
        parent::__construct($serializer);
43
        $this->options = $options;
44
45
        if (!isset($this->options['errorHandlers']['default'])) {
46
            throw new RouterException('Error Handlers must be specified');
47
        }
48
49
        $this->_checkCacheExpires();
50
    }
51
52
    /**
53
     * Check that cache file hasn't expired.
54
     * @return bool
55
     * @throws RouterException
56
     */
57
    protected function _checkCacheExpires()
58
    {
59
        if (isset($this->options['cacheExpires'], $this->options['cacheFile'])
60
            && $this->isCached()
61
        ) {
62
            $timeCacheCreated = \filemtime($this->options['cacheFile']);
63
            $timeNow = \time();
64
            $timeExpires = $timeCacheCreated + $this->options['cacheExpires'];
65
            if ($timeExpires - $timeNow < 0) {
66
                $removed = \unlink($this->options['cacheFile']);
67
                if (!$removed) {
68
                    throw new RouterException('Unable to delete file: ' . $this->options['cacheFile']);
69
                }
70
            }
71
        }
72
        return true;
73
    }
74
75
    /**
76
     * @inheritdoc
77
     */
78
    public function isCached()
79
    {
80
        $cacheDir = $this->options['cacheFile'] ?? __DIR__;
81
82
        $disabledCache = isset($this->options['cacheDisabled'])
83
            ? (bool)$this->options['cacheDisabled'] : true;
84
85
        if ($disabledCache === false) {
86
            return \file_exists($cacheDir) ? true : false;
87
        }
88
89
        return false;
90
    }
91
92
    /**
93
     * @inheritdoc
94
     */
95
    public function match(RouteCollectionInterface $routes, $method, $requestTarget)
96
    {
97
        $fastRouteResponse = [
98
            'statusCode' => 500,
99
            'allowedMethods' => [],
100
            'vars' => []
101
        ];
102
103
        $foundRoute = $this->_getCachedDispatcher($routes)->dispatch($method, $requestTarget);
104
105
        switch ($foundRoute[0]) {
106
            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...
107
                $fastRouteResponse['statusCode'] = 200;
108
                $fastRouteResponse['vars'] = $foundRoute[2];
109
                $fastRouteResponse['handler'] = $foundRoute[1];
110
                break;
111 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...
112
                $fastRouteResponse['statusCode'] = 404;
113
                $fastRouteResponse['handler'] = $this->_findOptionsHandler(404);
114
                break;
115 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...
116
                $fastRouteResponse['statusCode'] = 405;
117
                $fastRouteResponse['handler'] = $this->_findOptionsHandler(405);
118
                break;
119
            default:
120
                $fastRouteResponse['handler'] = $this->_findOptionsHandler('default');
121
        }
122
        return $this->_createRouterResponse($fastRouteResponse);
123
    }
124
125
    /**
126
     * Get Fast Route Cached Dispatcher.
127
     *
128
     * @param RouteCollectionInterface $routes
129
     * @param array $options
130
     * @return Dispatcher
131
     */
132
    protected function _getCachedDispatcher(RouteCollectionInterface $routes, array $options = array())
133
    {
134
        $options += $this->options;
135
        $this->options = $options;
136
137
        return \FastRoute\cachedDispatcher(
138
            function (RouteCollector $fastRouteCollector) use ($routes) {
139
                /**
140
                 * @var $route RouteInterface
141
                 */
142
                foreach ($routes as $route) {
143
                    $fastRouteCollector->addRoute(
144
                        $route->getMethod(),
145
                        $route->getPattern(),
146
                        $this->_createFastRouteHandler($route)
147
                    );
148
                }
149
            },
150
            $this->options
151
        );
152
    }
153
154
    /**
155
     * @param RouteInterface $route
156
     * @return array
157
     */
158 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...
159
    {
160
        $handlerOut = [
161
            'type' => $route->getHandlerType(),
162
            'name' => $route->getNames()
163
        ];
164
165
        if ($route->getHandlerType() === 'object') {
166
            $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...
167
        } else {
168
            $handlerOut['content'] = $route->getHandler();
169
        }
170
171
        return $handlerOut;
172
    }
173
174
    /**
175
     * Return handler if it exists.
176
     * @param $code
177
     * @return mixed
178
     */
179
    protected function _findOptionsHandler($code)
180
    {
181
        $handlerArgs = $this->options['errorHandlers']['default'];
182
183
        if (isset($this->options['errorHandlers'][$code])) {
184
            $handlerArgs = $this->options['errorHandlers'][$code];
185
        }
186
187
        return [
188
            'type' => 'string',
189
            'content' => $handlerArgs['handler'],
190
            'name' => $handlerArgs['name']
191
        ];
192
    }
193
194
    /**
195
     * Unserialize any cached content and return RouterResponse.
196
     *
197
     * @param $dispatchResponse
198
     * @return RouterResponse
199
     */
200 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...
201
    {
202
        if ($dispatchResponse['handler']['type'] === 'object') {
203
            $dispatchResponse['content'] = $this->serializer->unserialize($dispatchResponse['handler']['content']);
204
        } else {
205
            $dispatchResponse['content'] = $dispatchResponse['handler']['content'];
206
        }
207
208
        return new RouterResponse(
209
            $dispatchResponse['statusCode'],
210
            $dispatchResponse['content'],
211
            $dispatchResponse['handler']['name'],
212
            $dispatchResponse['vars']
213
        );
214
    }
215
216
    /**
217
     * @inheritdoc
218
     *
219
     * @throws \Rs\Router\Exceptions\UniqueRouteException
220
     */
221
    public function getCachedRoutes($context = '')
222
    {
223
        $collection = new RouteCollection();
224
225
        if ($this->isCached()) {
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 as $routeMethod) {
235
                foreach ((array)$routeMethod as $method => $route) {
236
                    foreach ((array)$route as $path => $routeData) {
237
                        $routePath = \rtrim($context, '/') . \DIRECTORY_SEPARATOR . \ltrim($path, '/') . $path;
238
                        $collection->addRoute($method, $routePath, $routeData['content'], $routeData['name']);
239
                    }
240
                }
241
            }
242
        }
243
        return $collection;
244
    }
245
}
246