Test Failed
Pull Request — master (#16)
by Divine Niiquaye
02:27
created

Router::isCached()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 0
cp 0
crap 2
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Flight Routing.
7
 *
8
 * PHP version 7.1 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Flight\Routing;
19
20
use Fig\Http\Message\RequestMethodInterface;
21
use Flight\Routing\Interfaces\RouteMatcherInterface;
22
use Laminas\Stratigility\{MiddlewarePipe, MiddlewarePipeInterface};
23
use Psr\Cache\CacheItemPoolInterface;
24
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface, UriInterface};
25
use Psr\Http\Server\{MiddlewareInterface, RequestHandlerInterface};
26
27
/**
28
 * Aggregate routes for matching and Dispatching.
29
 *
30
 * @author Divine Niiquaye Ibok <[email protected]>
31
 */
32
class Router implements RouteMatcherInterface, RequestMethodInterface, MiddlewareInterface
33
{
34
    /**
35
     * Standard HTTP methods for browser requests.
36
     */
37
    public const HTTP_METHODS_STANDARD = [
38
        self::METHOD_HEAD,
39
        self::METHOD_GET,
40
        self::METHOD_POST,
41
        self::METHOD_PUT,
42
        self::METHOD_PATCH,
43
        self::METHOD_DELETE,
44
        self::METHOD_PURGE,
45
        self::METHOD_OPTIONS,
46
        self::METHOD_TRACE,
47
        self::METHOD_CONNECT,
48
    ];
49
50
    /** @var MiddlewarePipeInterface */
51
    private $pipeline;
52
53
    /** @var RouteCollection|null */
54
    private $collection;
55
56
    /** @var RouteMatcher */
57
    protected $matcher;
58
59 73
    /** @var CacheItemPoolInterface|string */
60
    private $cacheData;
61
62
    /** @var bool */
63
    private $hasCached;
64
65 73
    /**
66 73
     * @param CacheItemPoolInterface|string $cacheFile use file path or PSR-6 cache
67 73
     */
68
    public function __construct(MiddlewarePipeInterface $dispatcher = null, $cache = '')
69 73
    {
70 73
        $this->pipeline = $dispatcher ?? new MiddlewarePipe();
71 73
72 73
        $this->hasCached = ($cache instanceof CacheItemPoolInterface && $cache->hasItem(__FILE__)) || \file_exists($cache);
73
        $this->cacheData = $cache;
74
    }
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function match(string $method, UriInterface $uri): ?Route
80
    {
81
        return $this->getMatcher()->match($method, $uri);
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     */
87
    public function matchRequest(ServerRequestInterface $request): ?Route
88 73
    {
89
        return $this->getMatcher()->matchRequest($request);
90 73
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function generateUri(string $routeName, array $parameters = []): GeneratedUri
96
    {
97
        return $this->getMatcher()->generateUri($routeName, $parameters);
98
    }
99
100 73
    /**
101
     * Attach middleware to the pipeline.
102 73
     */
103 73
    public function pipe(MiddlewareInterface ...$middlewares): void
104 73
    {
105
        foreach ($middlewares as $middleware) {
106
            $this->pipeline->pipe($middleware);
107
        }
108
    }
109
110 73
    /**
111
     * Sets the RouteCollection instance associated with this Router.
112
     */
113
    public function setCollection(RouteCollection $collection): void
114
    {
115
        $this->collection = $collection;
116 73
    }
117 3
118
    /**
119
     * Gets the RouteCollection instance associated with this Router.
120
     *
121 73
     * WARNING: This method should never be used at runtime as it is SLOW.
122 4
     *          You might use it in a cache warmer though.
123
     */
124 73
    public function getCollection(): RouteCollection
125
    {
126
        if (null === $this->collection) {
127
            throw new \RuntimeException('A RouteCollection instance is missing in router, did you forget to set it.');
128
        }
129 59
130
        return $this->collection;
131 59
    }
132 3
133
    /**
134
     * If RouteCollection's data has been cached.
135 56
     */
136
    public function isCached(): bool
137
    {
138
        return $this->hasCached;
139
    }
140
141
    /**
142
     * Gets the Route matcher instance associated with this Router.
143
     */
144
    public function getMatcher(): RouteMatcher
145 60
    {
146
        if (isset($this->matcher)) {
147 60
            return $this->matcher;
148 60
        }
149 29
150
        if ($this->hasCached) {
151
            return $this->matcher = new RouteMatcher(self::getCachedData($this->cacheData));
152 60
        }
153 1
154 1
        if ('' === $cache = $this->cacheData) {
155
            default_matcher:
156
            return $this->matcher = new RouteMatcher($this->getCollection());
157
        }
158 60
159
        $collection = $this->getCollection();
160 60
        $collectionData = $collection->getData();
161
162
        if ($cache instanceof CacheItemPoolInterface) {
163
            $cache->save($cache->getItem(__FILE__)->set([$collection->getCompiler(), $collectionData]));
164
        } else {
165
            $cachedData = \serialize([$collection->getCompiler(), $collectionData]);
166
167 24
            if (!\is_dir($directory = \dirname($cache))) {
168
                @\mkdir($directory, 0775, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mkdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

168
                /** @scrutinizer ignore-unhandled */ @\mkdir($directory, 0775, true);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
169 24
            }
170 2
171
            \file_put_contents($cache, "<?php // auto generated: AVOID MODIFYING\n\nreturn new Flight\Routing\CachedData(\unserialize('" . $cachedData . "'));\n");
172
        }
173 24
174 24
        goto default_matcher;
175
    }
176
177
    /**
178
     * {@inheritdoc}
179 12
     */
180
    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
181 12
    {
182
        $route = $this->getMatcher()->matchRequest($request);
183
184
        if (null !== $route && !empty($routeMiddlewares = $route->get('middlewares'))) {
185
            $this->pipe(...$routeMiddlewares);
186
        }
187
188
        try {
189 4
            return $this->pipeline->process($request->withAttribute(Route::class, $route), $handler);
190
        } finally {
191 4
            if (null !== $this->debug) {
0 ignored issues
show
Bug Best Practice introduced by
The property debug does not exist on Flight\Routing\Router. Did you maybe forget to declare it?
Loading history...
192
                $this->debug->leave();
193 4
            }
194
        }
195
    }
196
197
    /**
198
     * @param CacheItemPoolInterface|string $cache
199
     */
200
    private static function getCachedData($cache): CachedData
201
    {
202
        if ($cache instanceof CacheItemPoolInterface) {
203 55
            $cachedData = $cache->getItem(__FILE__)->get();
204
205
            if (!$cachedData instanceof CachedData) {
206 55
                $cache->deleteItem(__FILE__);
207
208 50
                throw new \RuntimeException('Failed to fetch cached routes data from PRS-6 cache pool, try reloading.');
209 4
            }
210 4
211 4
            return $cachedData;
212 4
        }
213
214
        $cachedData = require $cache;
215
216
        if (!$cachedData instanceof CachedData) {
217 46
            @\unlink($cache);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

217
            /** @scrutinizer ignore-unhandled */ @\unlink($cache);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
218
219 46
            throw new \RuntimeException(\sprintf('Failed to fetch cached routes data from "%s" file, try reloading.', $cache));
220 3
        }
221
222
        return $cachedData;
223 46
    }
224
}
225