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:38
created

Router::isCacheFileWritable()   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 View Code Duplication
        if ($cacheFile && file_exists($cacheFile) && !$this->isCacheFileWritable($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...
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...
140
            throw new RuntimeException(
141
                sprintf('Router cache file `%s` is not writable', $cacheFile)
142
            );
143
        }
144
145 View Code Duplication
        if ($cacheFile && !file_exists($cacheFile) && !$this->isCacheFileWritable(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...
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...
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
     * @param $cacheFile
157
     *
158
     * @return bool
159
     */
160
    protected function isCacheFileWritable($cacheFile)
161
    {
162
        return is_writable($cacheFile);
163
    }
164
165
    /**
166
     * @param ContainerInterface $container
167
     */
168
    public function setContainer(ContainerInterface $container)
169
    {
170
        $this->container = $container;
171
    }
172
173
    /**
174
     * Add route
175
     *
176
     * @param  string[] $methods Array of HTTP methods
177
     * @param  string   $pattern The route pattern
178
     * @param  callable $handler The route callable
179
     *
180
     * @return RouteInterface
181
     *
182
     * @throws InvalidArgumentException if the route pattern isn't a string
183
     */
184
    public function map($methods, $pattern, $handler)
185
    {
186
        if (!is_string($pattern)) {
187
            throw new InvalidArgumentException('Route pattern must be a string');
188
        }
189
190
        // Prepend parent group pattern(s)
191
        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...
192
            $pattern = $this->processGroups() . $pattern;
193
        }
194
195
        // According to RFC methods are defined in uppercase (See RFC 7231)
196
        $methods = array_map("strtoupper", $methods);
197
198
        /** @var \Slim\Route */
199
        $route = $this->createRoute($methods, $pattern, $handler);
200
        // Add route
201
        $this->routes[$route->getIdentifier()] = $route;
202
        $this->routeCounter++;
203
204
        return $route;
205
    }
206
207
    /**
208
     * Dispatch router for HTTP request
209
     *
210
     * @param  ServerRequestInterface $request The current HTTP request object
211
     *
212
     * @return array
213
     *
214
     * @link   https://github.com/nikic/FastRoute/blob/master/src/Dispatcher.php
215
     */
216
    public function dispatch(ServerRequestInterface $request)
217
    {
218
        $uri = '/' . ltrim($request->getUri()->getPath(), '/');
219
220
        return $this->createDispatcher()->dispatch(
221
            $request->getMethod(),
222
            $uri
223
        );
224
    }
225
226
    /**
227
     * Create a new Route object
228
     *
229
     * @param  string[] $methods Array of HTTP methods
230
     * @param  string   $pattern The route pattern
231
     * @param  callable $callable The route callable
232
     *
233
     * @return \Slim\Interfaces\RouteInterface
234
     */
235
    protected function createRoute($methods, $pattern, $callable)
236
    {
237
        $route = new Route($methods, $pattern, $callable, $this->routeGroups, $this->routeCounter);
238
        if (!empty($this->container)) {
239
            $route->setContainer($this->container);
240
        }
241
242
        return $route;
243
    }
244
245
    /**
246
     * @return \FastRoute\Dispatcher
247
     */
248
    protected function createDispatcher()
249
    {
250
        if ($this->dispatcher) {
251
            return $this->dispatcher;
252
        }
253
254
        $routeDefinitionCallback = function (RouteCollector $r) {
255
            foreach ($this->getRoutes() as $route) {
256
                $r->addRoute($route->getMethods(), $route->getPattern(), $route->getIdentifier());
257
            }
258
        };
259
260
        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...
261
            $this->dispatcher = \FastRoute\cachedDispatcher($routeDefinitionCallback, [
262
                'routeParser' => $this->routeParser,
263
                'cacheFile' => $this->cacheFile,
264
            ]);
265
        } else {
266
            $this->dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback, [
267
                'routeParser' => $this->routeParser,
268
            ]);
269
        }
270
271
        return $this->dispatcher;
272
    }
273
274
    /**
275
     * @param \FastRoute\Dispatcher $dispatcher
276
     */
277
    public function setDispatcher(Dispatcher $dispatcher)
278
    {
279
        $this->dispatcher = $dispatcher;
280
    }
281
282
    /**
283
     * Get route objects
284
     *
285
     * @return Route[]
286
     */
287
    public function getRoutes()
288
    {
289
        return $this->routes;
290
    }
291
292
    /**
293
     * Get named route object
294
     *
295
     * @param string $name        Route name
296
     *
297
     * @return Route
298
     *
299
     * @throws RuntimeException   If named route does not exist
300
     */
301
    public function getNamedRoute($name)
302
    {
303
        foreach ($this->routes as $route) {
304
            if ($name == $route->getName()) {
305
                return $route;
306
            }
307
        }
308
        throw new RuntimeException('Named route does not exist for name: ' . $name);
309
    }
310
311
    /**
312
     * Remove named route
313
     *
314
     * @param string $name        Route name
315
     *
316
     * @throws RuntimeException   If named route does not exist
317
     */
318
    public function removeNamedRoute($name)
319
    {
320
        $route = $this->getNamedRoute($name);
321
322
        // no exception, route exists, now remove by id
323
        unset($this->routes[$route->getIdentifier()]);
324
    }
325
326
    /**
327
     * Process route groups
328
     *
329
     * @return string A group pattern to prefix routes with
330
     */
331
    protected function processGroups()
332
    {
333
        $pattern = "";
334
        foreach ($this->routeGroups as $group) {
335
            $pattern .= $group->getPattern();
336
        }
337
        return $pattern;
338
    }
339
340
    /**
341
     * Add a route group to the array
342
     *
343
     * @param string   $pattern
344
     * @param callable $callable
345
     *
346
     * @return RouteGroupInterface
347
     */
348
    public function pushGroup($pattern, $callable)
349
    {
350
        $group = new RouteGroup($pattern, $callable);
351
        array_push($this->routeGroups, $group);
352
        return $group;
353
    }
354
355
    /**
356
     * Removes the last route group from the array
357
     *
358
     * @return RouteGroup|bool The RouteGroup if successful, else False
359
     */
360
    public function popGroup()
361
    {
362
        $group = array_pop($this->routeGroups);
363
        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 363 which is incompatible with the return type declared by the interface Slim\Interfaces\RouterInterface::popGroup of type boolean.
Loading history...
364
    }
365
366
    /**
367
     *
368
     * @param string $identifier
369
     * @return \Slim\Interfaces\RouteInterface
370
     */
371
    public function lookupRoute($identifier)
372
    {
373
        if (!isset($this->routes[$identifier])) {
374
            throw new RuntimeException('Route not found, looks like your route cache is stale.');
375
        }
376
        return $this->routes[$identifier];
377
    }
378
379
    /**
380
     * Build the path for a named route excluding the base path
381
     *
382
     * @param string $name        Route name
383
     * @param array  $data        Named argument replacement data
384
     * @param array  $queryParams Optional query string parameters
385
     *
386
     * @return string
387
     *
388
     * @throws RuntimeException         If named route does not exist
389
     * @throws InvalidArgumentException If required data not provided
390
     */
391
    public function relativePathFor($name, array $data = [], array $queryParams = [])
392
    {
393
        $route = $this->getNamedRoute($name);
394
        $pattern = $route->getPattern();
395
396
        $routeDatas = $this->routeParser->parse($pattern);
397
        // $routeDatas is an array of all possible routes that can be made. There is
398
        // one routedata for each optional parameter plus one for no optional parameters.
399
        //
400
        // The most specific is last, so we look for that first.
401
        $routeDatas = array_reverse($routeDatas);
402
403
        $segments = [];
404
        $segmentName = '';
405
        foreach ($routeDatas as $routeData) {
406
            foreach ($routeData as $item) {
407
                if (is_string($item)) {
408
                    // this segment is a static string
409
                    $segments[] = $item;
410
                    continue;
411
                }
412
413
                // This segment has a parameter: first element is the name
414
                if (!array_key_exists($item[0], $data)) {
415
                    // we don't have a data element for this segment: cancel
416
                    // testing this routeData item, so that we can try a less
417
                    // specific routeData item.
418
                    $segments = [];
419
                    $segmentName = $item[0];
420
                    break;
421
                }
422
                $segments[] = $data[$item[0]];
423
            }
424
            if (!empty($segments)) {
425
                // we found all the parameters for this route data, no need to check
426
                // less specific ones
427
                break;
428
            }
429
        }
430
431
        if (empty($segments)) {
432
            throw new InvalidArgumentException('Missing data for URL segment: ' . $segmentName);
433
        }
434
        $url = implode('', $segments);
435
436
        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...
437
            $url .= '?' . http_build_query($queryParams);
438
        }
439
440
        return $url;
441
    }
442
443
444
    /**
445
     * Build the path for a named route including the base path
446
     *
447
     * @param string $name        Route name
448
     * @param array  $data        Named argument replacement data
449
     * @param array  $queryParams Optional query string parameters
450
     *
451
     * @return string
452
     *
453
     * @throws RuntimeException         If named route does not exist
454
     * @throws InvalidArgumentException If required data not provided
455
     */
456
    public function pathFor($name, array $data = [], array $queryParams = [])
457
    {
458
        $url = $this->relativePathFor($name, $data, $queryParams);
459
460
        if ($this->basePath) {
461
            $url = $this->basePath . $url;
462
        }
463
464
        return $url;
465
    }
466
467
    /**
468
     * Build the path for a named route.
469
     *
470
     * This method is deprecated. Use pathFor() from now on.
471
     *
472
     * @param string $name        Route name
473
     * @param array  $data        Named argument replacement data
474
     * @param array  $queryParams Optional query string parameters
475
     *
476
     * @return string
477
     *
478
     * @throws RuntimeException         If named route does not exist
479
     * @throws InvalidArgumentException If required data not provided
480
     */
481
    public function urlFor($name, array $data = [], array $queryParams = [])
482
    {
483
        trigger_error('urlFor() is deprecated. Use pathFor() instead.', E_USER_DEPRECATED);
484
        return $this->pathFor($name, $data, $queryParams);
485
    }
486
}
487