GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — 3.x (#2586)
by
unknown
01:42
created

Router::isCacheFileReadable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * Slim Framework (https://slimframework.com)
4
 *
5
 * @link      https://github.com/slimphp/Slim
6
 * @copyright Copyright (c) 2011-2017 Josh Lockhart
7
 * @license   https://github.com/slimphp/Slim/blob/3.x/LICENSE.md (MIT License)
8
 */
9
namespace Slim;
10
11
use FastRoute\Dispatcher;
12
use Psr\Container\ContainerInterface;
13
use InvalidArgumentException;
14
use RuntimeException;
15
use Psr\Http\Message\ServerRequestInterface;
16
use FastRoute\RouteCollector;
17
use FastRoute\RouteParser;
18
use FastRoute\RouteParser\Std as StdParser;
19
use Slim\Interfaces\RouteGroupInterface;
20
use Slim\Interfaces\RouterInterface;
21
use Slim\Interfaces\RouteInterface;
22
23
/**
24
 * Router
25
 *
26
 * This class organizes Slim application route objects. It is responsible
27
 * for registering route objects, assigning names to route objects,
28
 * finding routes that match the current HTTP request, and creating
29
 * URLs for a named route.
30
 */
31
class Router implements RouterInterface
32
{
33
    /**
34
     * Container Interface
35
     *
36
     * @var ContainerInterface
37
     */
38
    protected $container;
39
40
    /**
41
     * Parser
42
     *
43
     * @var \FastRoute\RouteParser
44
     */
45
    protected $routeParser;
46
47
    /**
48
     * Base path used in pathFor()
49
     *
50
     * @var string
51
     */
52
    protected $basePath = '';
53
54
    /**
55
     * Path to fast route cache file. Set to false to disable route caching
56
     *
57
     * @var string|False
58
     */
59
    protected $cacheFile = false;
60
61
    /**
62
     * Routes
63
     *
64
     * @var Route[]
65
     */
66
    protected $routes = [];
67
68
    /**
69
     * Route counter incrementer
70
     * @var int
71
     */
72
    protected $routeCounter = 0;
73
74
    /**
75
     * Route groups
76
     *
77
     * @var RouteGroup[]
78
     */
79
    protected $routeGroups = [];
80
81
    /**
82
     * @var \FastRoute\Dispatcher
83
     */
84
    protected $dispatcher;
85
86
    /**
87
     * Create new router
88
     *
89
     * @param RouteParser   $parser
90
     */
91
    public function __construct(RouteParser $parser = null)
92
    {
93
        $this->routeParser = $parser ?: new StdParser;
94
    }
95
96
    /**
97
     * Set the base path used in pathFor()
98
     *
99
     * @param string $basePath
100
     *
101
     * @return self
102
     */
103
    public function setBasePath($basePath)
104
    {
105
        if (!is_string($basePath)) {
106
            throw new InvalidArgumentException('Router basePath must be a string');
107
        }
108
109
        $this->basePath = $basePath;
110
111
        return $this;
112
    }
113
114
    /**
115
     * Get the base path used in pathFor()
116
     *
117
     * @return string
118
     */
119
    public function getBasePath()
120
    {
121
        return $this->basePath;
122
    }
123
124
    /**
125
     * Set path to fast route cache file. If this is false then route caching is disabled.
126
     *
127
     * @param string|false $cacheFile
128
     *
129
     * @return self
130
     */
131
    public function setCacheFile($cacheFile)
132
    {
133
        $this->cacheFile = $cacheFile;
134
135
        if (!is_string($cacheFile) && $cacheFile !== false) {
136
            throw new InvalidArgumentException('Router cache file must be a string or false');
137
        }
138
139
        if ($cacheFile && file_exists($cacheFile) && !$this->isCacheFileReadable($cacheFile)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cacheFile of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
140
            throw new RuntimeException(
141
                sprintf('Router cache file `%s` is not readable', $cacheFile)
142
            );
143
        }
144
145
        if ($cacheFile && !file_exists($cacheFile) && !$this->isCacheDirWritable(dirname($cacheFile))) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $cacheFile of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
146
            throw new RuntimeException(
147
                sprintf('Router cache file directory `%s` is not writable', dirname($cacheFile))
148
            );
149
        }
150
151
        return $this;
152
    }
153
154
    /**
155
     * This method is in place to facilitate unit tests
156
     *
157
     * @param $cacheFile
158
     *
159
     * @return bool
160
     */
161
    protected function isCacheFileReadable($cacheFile)
162
    {
163
        return is_readable($cacheFile);
164
    }
165
166
    /**
167
     * This method is in place to facilitate unit tests
168
     *
169
     * @param $cacheDir
170
     *
171
     * @return bool
172
     */
173
    protected function isCacheDirWritable($cacheDir)
174
    {
175
        return is_writable($cacheDir);
176
    }
177
178
    /**
179
     * @param ContainerInterface $container
180
     */
181
    public function setContainer(ContainerInterface $container)
182
    {
183
        $this->container = $container;
184
    }
185
186
    /**
187
     * Add route
188
     *
189
     * @param  string[] $methods Array of HTTP methods
190
     * @param  string   $pattern The route pattern
191
     * @param  callable $handler The route callable
192
     *
193
     * @return RouteInterface
194
     *
195
     * @throws InvalidArgumentException if the route pattern isn't a string
196
     */
197
    public function map($methods, $pattern, $handler)
198
    {
199
        if (!is_string($pattern)) {
200
            throw new InvalidArgumentException('Route pattern must be a string');
201
        }
202
203
        // Prepend parent group pattern(s)
204
        if ($this->routeGroups) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->routeGroups of type Slim\RouteGroup[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
205
            $pattern = $this->processGroups() . $pattern;
206
        }
207
208
        // According to RFC methods are defined in uppercase (See RFC 7231)
209
        $methods = array_map("strtoupper", $methods);
210
211
        /** @var \Slim\Route */
212
        $route = $this->createRoute($methods, $pattern, $handler);
213
        // Add route
214
        $this->routes[$route->getIdentifier()] = $route;
215
        $this->routeCounter++;
216
217
        return $route;
218
    }
219
220
    /**
221
     * Dispatch router for HTTP request
222
     *
223
     * @param  ServerRequestInterface $request The current HTTP request object
224
     *
225
     * @return array
226
     *
227
     * @link   https://github.com/nikic/FastRoute/blob/master/src/Dispatcher.php
228
     */
229
    public function dispatch(ServerRequestInterface $request)
230
    {
231
        $uri = '/' . ltrim($request->getUri()->getPath(), '/');
232
233
        return $this->createDispatcher()->dispatch(
234
            $request->getMethod(),
235
            $uri
236
        );
237
    }
238
239
    /**
240
     * Create a new Route object
241
     *
242
     * @param  string[] $methods Array of HTTP methods
243
     * @param  string   $pattern The route pattern
244
     * @param  callable $callable The route callable
245
     *
246
     * @return \Slim\Interfaces\RouteInterface
247
     */
248
    protected function createRoute($methods, $pattern, $callable)
249
    {
250
        $route = new Route($methods, $pattern, $callable, $this->routeGroups, $this->routeCounter);
251
        if (!empty($this->container)) {
252
            $route->setContainer($this->container);
253
        }
254
255
        return $route;
256
    }
257
258
    /**
259
     * @return \FastRoute\Dispatcher
260
     */
261
    protected function createDispatcher()
262
    {
263
        if ($this->dispatcher) {
264
            return $this->dispatcher;
265
        }
266
267
        $routeDefinitionCallback = function (RouteCollector $r) {
268
            foreach ($this->getRoutes() as $route) {
269
                $r->addRoute($route->getMethods(), $route->getPattern(), $route->getIdentifier());
270
            }
271
        };
272
273
        if ($this->cacheFile) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cacheFile of type string|false is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
274
            $this->dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [
275
                'routeParser' => $this->routeParser,
276
                'cacheFile' => $this->cacheFile,
277
            ]);
278
        } else {
279
            $this->dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [
280
                'routeParser' => $this->routeParser,
281
            ]);
282
        }
283
284
        return $this->dispatcher;
285
    }
286
287
    /**
288
     * @param \FastRoute\Dispatcher $dispatcher
289
     */
290
    public function setDispatcher(Dispatcher $dispatcher)
291
    {
292
        $this->dispatcher = $dispatcher;
293
    }
294
295
    /**
296
     * Get route objects
297
     *
298
     * @return Route[]
299
     */
300
    public function getRoutes()
301
    {
302
        return $this->routes;
303
    }
304
305
    /**
306
     * Get named route object
307
     *
308
     * @param string $name        Route name
309
     *
310
     * @return Route
311
     *
312
     * @throws RuntimeException   If named route does not exist
313
     */
314
    public function getNamedRoute($name)
315
    {
316
        foreach ($this->routes as $route) {
317
            if ($name == $route->getName()) {
318
                return $route;
319
            }
320
        }
321
        throw new RuntimeException('Named route does not exist for name: ' . $name);
322
    }
323
324
    /**
325
     * Remove named route
326
     *
327
     * @param string $name        Route name
328
     *
329
     * @throws RuntimeException   If named route does not exist
330
     */
331
    public function removeNamedRoute($name)
332
    {
333
        $route = $this->getNamedRoute($name);
334
335
        // no exception, route exists, now remove by id
336
        unset($this->routes[$route->getIdentifier()]);
337
    }
338
339
    /**
340
     * Process route groups
341
     *
342
     * @return string A group pattern to prefix routes with
343
     */
344
    protected function processGroups()
345
    {
346
        $pattern = "";
347
        foreach ($this->routeGroups as $group) {
348
            $pattern .= $group->getPattern();
349
        }
350
        return $pattern;
351
    }
352
353
    /**
354
     * Add a route group to the array
355
     *
356
     * @param string   $pattern
357
     * @param callable $callable
358
     *
359
     * @return RouteGroupInterface
360
     */
361
    public function pushGroup($pattern, $callable)
362
    {
363
        $group = new RouteGroup($pattern, $callable);
364
        array_push($this->routeGroups, $group);
365
        return $group;
366
    }
367
368
    /**
369
     * Removes the last route group from the array
370
     *
371
     * @return RouteGroup|bool The RouteGroup if successful, else False
372
     */
373
    public function popGroup()
374
    {
375
        $group = array_pop($this->routeGroups);
376
        return $group instanceof RouteGroup ? $group : false;
0 ignored issues
show
Bug Compatibility introduced by
The expression $group instanceof \Slim\...Group ? $group : false; of type Slim\RouteGroup|false adds the type Slim\RouteGroup to the return on line 376 which is incompatible with the return type declared by the interface Slim\Interfaces\RouterInterface::popGroup of type boolean.
Loading history...
377
    }
378
379
    /**
380
     *
381
     * @param string $identifier
382
     * @return \Slim\Interfaces\RouteInterface
383
     */
384
    public function lookupRoute($identifier)
385
    {
386
        if (!isset($this->routes[$identifier])) {
387
            throw new RuntimeException('Route not found, looks like your route cache is stale.');
388
        }
389
        return $this->routes[$identifier];
390
    }
391
392
    /**
393
     * Build the path for a named route excluding the base path
394
     *
395
     * @param string $name        Route name
396
     * @param array  $data        Named argument replacement data
397
     * @param array  $queryParams Optional query string parameters
398
     *
399
     * @return string
400
     *
401
     * @throws RuntimeException         If named route does not exist
402
     * @throws InvalidArgumentException If required data not provided
403
     */
404
    public function relativePathFor($name, array $data = [], array $queryParams = [])
405
    {
406
        $route = $this->getNamedRoute($name);
407
        $pattern = $route->getPattern();
408
409
        $routeDatas = $this->routeParser->parse($pattern);
410
        // $routeDatas is an array of all possible routes that can be made. There is
411
        // one routedata for each optional parameter plus one for no optional parameters.
412
        //
413
        // The most specific is last, so we look for that first.
414
        $routeDatas = array_reverse($routeDatas);
415
416
        $segments = [];
417
        $segmentName = '';
418
        foreach ($routeDatas as $routeData) {
419
            foreach ($routeData as $item) {
420
                if (is_string($item)) {
421
                    // this segment is a static string
422
                    $segments[] = $item;
423
                    continue;
424
                }
425
426
                // This segment has a parameter: first element is the name
427
                if (!array_key_exists($item[0], $data)) {
428
                    // we don't have a data element for this segment: cancel
429
                    // testing this routeData item, so that we can try a less
430
                    // specific routeData item.
431
                    $segments = [];
432
                    $segmentName = $item[0];
433
                    break;
434
                }
435
                $segments[] = $data[$item[0]];
436
            }
437
            if (!empty($segments)) {
438
                // we found all the parameters for this route data, no need to check
439
                // less specific ones
440
                break;
441
            }
442
        }
443
444
        if (empty($segments)) {
445
            throw new InvalidArgumentException('Missing data for URL segment: ' . $segmentName);
446
        }
447
        $url = implode('', $segments);
448
449
        if ($queryParams) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $queryParams of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
450
            $url .= '?' . http_build_query($queryParams);
451
        }
452
453
        return $url;
454
    }
455
456
457
    /**
458
     * Build the path for a named route including the base path
459
     *
460
     * @param string $name        Route name
461
     * @param array  $data        Named argument replacement data
462
     * @param array  $queryParams Optional query string parameters
463
     *
464
     * @return string
465
     *
466
     * @throws RuntimeException         If named route does not exist
467
     * @throws InvalidArgumentException If required data not provided
468
     */
469
    public function pathFor($name, array $data = [], array $queryParams = [])
470
    {
471
        $url = $this->relativePathFor($name, $data, $queryParams);
472
473
        if ($this->basePath) {
474
            $url = $this->basePath . $url;
475
        }
476
477
        return $url;
478
    }
479
480
    /**
481
     * Build the path for a named route.
482
     *
483
     * This method is deprecated. Use pathFor() from now on.
484
     *
485
     * @param string $name        Route name
486
     * @param array  $data        Named argument replacement data
487
     * @param array  $queryParams Optional query string parameters
488
     *
489
     * @return string
490
     *
491
     * @throws RuntimeException         If named route does not exist
492
     * @throws InvalidArgumentException If required data not provided
493
     */
494
    public function urlFor($name, array $data = [], array $queryParams = [])
495
    {
496
        trigger_error('urlFor() is deprecated. Use pathFor() instead.', E_USER_DEPRECATED);
497
        return $this->pathFor($name, $data, $queryParams);
498
    }
499
}
500