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 (#2141)
by Ian
03:06 queued 59s
created

App::processInvalidMethod()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 12

Duplication

Lines 3
Ratio 15 %

Importance

Changes 0
Metric Value
dl 3
loc 20
rs 9.2
c 0
b 0
f 0
cc 4
eloc 12
nc 4
nop 2
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 Exception;
12
use Slim\Exception\InvalidMethodException;
13
use Slim\Http\Response;
14
use Throwable;
15
use Closure;
16
use InvalidArgumentException;
17
use Psr\Http\Message\RequestInterface;
18
use Psr\Http\Message\ServerRequestInterface;
19
use Psr\Http\Message\ResponseInterface;
20
use Interop\Container\ContainerInterface;
21
use FastRoute\Dispatcher;
22
use Slim\Exception\SlimException;
23
use Slim\Exception\MethodNotAllowedException;
24
use Slim\Exception\NotFoundException;
25
use Slim\Http\Uri;
26
use Slim\Http\Headers;
27
use Slim\Http\Body;
28
use Slim\Http\Request;
29
use Slim\Interfaces\Http\EnvironmentInterface;
30
use Slim\Interfaces\RouteGroupInterface;
31
use Slim\Interfaces\RouteInterface;
32
use Slim\Interfaces\RouterInterface;
33
34
/**
35
 * App
36
 *
37
 * This is the primary class with which you instantiate,
38
 * configure, and run a Slim Framework application.
39
 * The \Slim\App class also accepts Slim Framework middleware.
40
 *
41
 * @property-read callable $errorHandler
42
 * @property-read callable $phpErrorHandler
43
 * @property-read callable $notFoundHandler function($request, $response)
44
 * @property-read callable $notAllowedHandler function($request, $response, $allowedHttpMethods)
45
 */
46
class App
47
{
48
    use MiddlewareAwareTrait;
49
50
    /**
51
     * Current version
52
     *
53
     * @var string
54
     */
55
    const VERSION = '3.8.0-dev';
56
57
    /**
58
     * Container
59
     *
60
     * @var ContainerInterface
61
     */
62
    private $container;
63
64
    /********************************************************************************
65
     * Constructor
66
     *******************************************************************************/
67
68
    /**
69
     * Create new application
70
     *
71
     * @param ContainerInterface|array $container Either a ContainerInterface or an associative array of app settings
72
     * @throws InvalidArgumentException when no container is provided that implements ContainerInterface
73
     */
74
    public function __construct($container = [])
75
    {
76
        if (is_array($container)) {
77
            $container = new Container($container);
78
        }
79
        if (!$container instanceof ContainerInterface) {
80
            throw new InvalidArgumentException('Expected a ContainerInterface');
81
        }
82
        $this->container = $container;
83
    }
84
85
    /**
86
     * Enable access to the DI container by consumers of $app
87
     *
88
     * @return ContainerInterface
89
     */
90
    public function getContainer()
91
    {
92
        return $this->container;
93
    }
94
95
    /**
96
     * Add middleware
97
     *
98
     * This method prepends new middleware to the app's middleware stack.
99
     *
100
     * @param  callable|string    $callable The callback routine
101
     *
102
     * @return static
103
     */
104
    public function add($callable)
105
    {
106
        return $this->addMiddleware(new DeferredCallable($callable, $this->container));
107
    }
108
109
    /**
110
     * Calling a non-existant method on App checks to see if there's an item
111
     * in the container that is callable and if so, calls it.
112
     *
113
     * @param  string $method
114
     * @param  array $args
115
     * @return mixed
116
     */
117
    public function __call($method, $args)
118
    {
119
        if ($this->container->has($method)) {
120
            $obj = $this->container->get($method);
121
            if (is_callable($obj)) {
122
                return call_user_func_array($obj, $args);
123
            }
124
        }
125
126
        throw new \BadMethodCallException("Method $method is not a valid method");
127
    }
128
129
    /********************************************************************************
130
     * Router proxy methods
131
     *******************************************************************************/
132
133
    /**
134
     * Add GET route
135
     *
136
     * @param  string $pattern  The route URI pattern
137
     * @param  callable|string  $callable The route callback routine
138
     *
139
     * @return \Slim\Interfaces\RouteInterface
140
     */
141
    public function get($pattern, $callable)
142
    {
143
        return $this->map(['GET'], $pattern, $callable);
144
    }
145
146
    /**
147
     * Add POST route
148
     *
149
     * @param  string $pattern  The route URI pattern
150
     * @param  callable|string  $callable The route callback routine
151
     *
152
     * @return \Slim\Interfaces\RouteInterface
153
     */
154
    public function post($pattern, $callable)
155
    {
156
        return $this->map(['POST'], $pattern, $callable);
157
    }
158
159
    /**
160
     * Add PUT route
161
     *
162
     * @param  string $pattern  The route URI pattern
163
     * @param  callable|string  $callable The route callback routine
164
     *
165
     * @return \Slim\Interfaces\RouteInterface
166
     */
167
    public function put($pattern, $callable)
168
    {
169
        return $this->map(['PUT'], $pattern, $callable);
170
    }
171
172
    /**
173
     * Add PATCH route
174
     *
175
     * @param  string $pattern  The route URI pattern
176
     * @param  callable|string  $callable The route callback routine
177
     *
178
     * @return \Slim\Interfaces\RouteInterface
179
     */
180
    public function patch($pattern, $callable)
181
    {
182
        return $this->map(['PATCH'], $pattern, $callable);
183
    }
184
185
    /**
186
     * Add DELETE route
187
     *
188
     * @param  string $pattern  The route URI pattern
189
     * @param  callable|string  $callable The route callback routine
190
     *
191
     * @return \Slim\Interfaces\RouteInterface
192
     */
193
    public function delete($pattern, $callable)
194
    {
195
        return $this->map(['DELETE'], $pattern, $callable);
196
    }
197
198
    /**
199
     * Add OPTIONS route
200
     *
201
     * @param  string $pattern  The route URI pattern
202
     * @param  callable|string  $callable The route callback routine
203
     *
204
     * @return \Slim\Interfaces\RouteInterface
205
     */
206
    public function options($pattern, $callable)
207
    {
208
        return $this->map(['OPTIONS'], $pattern, $callable);
209
    }
210
211
    /**
212
     * Add route for any HTTP method
213
     *
214
     * @param  string $pattern  The route URI pattern
215
     * @param  callable|string  $callable The route callback routine
216
     *
217
     * @return \Slim\Interfaces\RouteInterface
218
     */
219
    public function any($pattern, $callable)
220
    {
221
        return $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $pattern, $callable);
222
    }
223
224
    /**
225
     * Add route with multiple methods
226
     *
227
     * @param  string[] $methods  Numeric array of HTTP method names
228
     * @param  string   $pattern  The route URI pattern
229
     * @param  callable|string    $callable The route callback routine
230
     *
231
     * @return RouteInterface
232
     */
233
    public function map(array $methods, $pattern, $callable)
234
    {
235
        if ($callable instanceof Closure) {
236
            $callable = $callable->bindTo($this->container);
237
        }
238
239
        $route = $this->container->get('router')->map($methods, $pattern, $callable);
240
        if (is_callable([$route, 'setContainer'])) {
241
            $route->setContainer($this->container);
242
        }
243
244
        if (is_callable([$route, 'setOutputBuffering'])) {
245
            $route->setOutputBuffering($this->container->get('settings')['outputBuffering']);
246
        }
247
248
        return $route;
249
    }
250
251
    /**
252
     * Route Groups
253
     *
254
     * This method accepts a route pattern and a callback. All route
255
     * declarations in the callback will be prepended by the group(s)
256
     * that it is in.
257
     *
258
     * @param string   $pattern
259
     * @param callable $callable
260
     *
261
     * @return RouteGroupInterface
262
     */
263
    public function group($pattern, $callable)
264
    {
265
        /** @var RouteGroup $group */
266
        $group = $this->container->get('router')->pushGroup($pattern, $callable);
267
        $group->setContainer($this->container);
268
        $group($this);
269
        $this->container->get('router')->popGroup();
270
        return $group;
271
    }
272
273
    /********************************************************************************
274
     * Runner
275
     *******************************************************************************/
276
277
    /**
278
     * Run application
279
     *
280
     * This method traverses the application middleware stack and then sends the
281
     * resultant Response object to the HTTP client.
282
     *
283
     * @param bool|false $silent
284
     * @return ResponseInterface
285
     *
286
     * @throws Exception
287
     * @throws MethodNotAllowedException
288
     * @throws NotFoundException
289
     */
290
    public function run($silent = false)
291
    {
292
        try {
293
            $request = $this->container->get('request');
294
        } catch (InvalidMethodException $e) {
295
            $request = $e->getRequest();
296
        }
297
298
        $response = $this->container->get('response');
299
300
        $response = !isset($e) ? $this->process($request, $response) : $this->processInvalidMethod($request, $response);
301
302
        if (!$silent) {
303
            $this->respond($response);
304
        }
305
306
        return $response;
307
    }
308
309
    /**
310
     * Pull route info for a request with a bad method to decide whether to
311
     * return a not-found error (default) or a bad-method error, then run
312
     * the handler for that error, returning the resulting response.
313
     *
314
     * Used for cases where an incoming request has an unrecognized method,
315
     * rather than throwing an exception and not catching it all the way up.
316
     *
317
     * @param ServerRequestInterface $request
318
     * @param ResponseInterface $response
319
     * @return ResponseInterface
320
     */
321
    protected function processInvalidMethod(ServerRequestInterface $request, ResponseInterface $response)
322
    {
323
        $router = $this->container->get('router');
324 View Code Duplication
        if (is_callable([$request->getUri(), 'getBasePath']) && is_callable([$router, 'setBasePath'])) {
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...
325
            $router->setBasePath($request->getUri()->getBasePath());
326
        }
327
328
        $request = $this->dispatchRouterAndPrepareRoute($request, $router);
329
        $routeInfo = $request->getAttribute('routeInfo', [RouterInterface::DISPATCH_STATUS => Dispatcher::NOT_FOUND]);
330
331
        if ($routeInfo[RouterInterface::DISPATCH_STATUS] === Dispatcher::METHOD_NOT_ALLOWED) {
332
            return $this->handleException(
333
                new MethodNotAllowedException($request, $response, $routeInfo[RouterInterface::ALLOWED_METHODS]),
334
                $request,
335
                $response
336
            );
337
        }
338
339
        return $this->handleException(new NotFoundException($request, $response), $request, $response);
340
    }
341
342
    /**
343
     * Process a request
344
     *
345
     * This method traverses the application middleware stack and then returns the
346
     * resultant Response object.
347
     *
348
     * @param ServerRequestInterface $request
349
     * @param ResponseInterface $response
350
     * @return ResponseInterface
351
     *
352
     * @throws Exception
353
     * @throws MethodNotAllowedException
354
     * @throws NotFoundException
355
     */
356
    public function process(ServerRequestInterface $request, ResponseInterface $response)
357
    {
358
        // Ensure basePath is set
359
        $router = $this->container->get('router');
360 View Code Duplication
        if (is_callable([$request->getUri(), 'getBasePath']) && is_callable([$router, 'setBasePath'])) {
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...
361
            $router->setBasePath($request->getUri()->getBasePath());
362
        }
363
364
        // Dispatch the Router first if the setting for this is on
365
        if ($this->container->get('settings')['determineRouteBeforeAppMiddleware'] === true) {
366
            // Dispatch router (note: you won't be able to alter routes after this)
367
            $request = $this->dispatchRouterAndPrepareRoute($request, $router);
368
        }
369
370
        // Traverse middleware stack
371
        try {
372
            $response = $this->callMiddlewareStack($request, $response);
373
        } catch (Exception $e) {
374
            $response = $this->handleException($e, $request, $response);
375
        } catch (Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
376
            $response = $this->handlePhpError($e, $request, $response);
377
        }
378
379
        $response = $this->finalize($response);
380
381
        return $response;
382
    }
383
384
    /**
385
     * Send the response the client
386
     *
387
     * @param ResponseInterface $response
388
     */
389
    public function respond(ResponseInterface $response)
390
    {
391
        // Send response
392
        if (!headers_sent()) {
393
            // Status
394
            header(sprintf(
395
                'HTTP/%s %s %s',
396
                $response->getProtocolVersion(),
397
                $response->getStatusCode(),
398
                $response->getReasonPhrase()
399
            ));
400
401
            // Headers
402
            foreach ($response->getHeaders() as $name => $values) {
403
                foreach ($values as $value) {
404
                    header(sprintf('%s: %s', $name, $value), false);
405
                }
406
            }
407
        }
408
409
        // Body
410
        if (!$this->isEmptyResponse($response)) {
411
            $body = $response->getBody();
412
            if ($body->isSeekable()) {
413
                $body->rewind();
414
            }
415
            $settings       = $this->container->get('settings');
416
            $chunkSize      = $settings['responseChunkSize'];
417
418
            $contentLength  = $response->getHeaderLine('Content-Length');
419
            if (!$contentLength) {
420
                $contentLength = $body->getSize();
421
            }
422
423
424
            if (isset($contentLength)) {
425
                $amountToRead = $contentLength;
426
                while ($amountToRead > 0 && !$body->eof()) {
427
                    $data = $body->read(min($chunkSize, $amountToRead));
428
                    echo $data;
429
430
                    $amountToRead -= strlen($data);
431
432
                    if (connection_status() != CONNECTION_NORMAL) {
433
                        break;
434
                    }
435
                }
436
            } else {
437
                while (!$body->eof()) {
438
                    echo $body->read($chunkSize);
439
                    if (connection_status() != CONNECTION_NORMAL) {
440
                        break;
441
                    }
442
                }
443
            }
444
        }
445
    }
446
447
    /**
448
     * Invoke application
449
     *
450
     * This method implements the middleware interface. It receives
451
     * Request and Response objects, and it returns a Response object
452
     * after compiling the routes registered in the Router and dispatching
453
     * the Request object to the appropriate Route callback routine.
454
     *
455
     * @param  ServerRequestInterface $request  The most recent Request object
456
     * @param  ResponseInterface      $response The most recent Response object
457
     *
458
     * @return ResponseInterface
459
     * @throws MethodNotAllowedException
460
     * @throws NotFoundException
461
     */
462
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
463
    {
464
        // Get the route info
465
        $routeInfo = $request->getAttribute('routeInfo');
466
467
        /** @var \Slim\Interfaces\RouterInterface $router */
468
        $router = $this->container->get('router');
469
470
        // If router hasn't been dispatched or the URI changed then dispatch
471
        if (null === $routeInfo || ($routeInfo['request'] !== [$request->getMethod(), (string) $request->getUri()])) {
472
            $request = $this->dispatchRouterAndPrepareRoute($request, $router);
473
            $routeInfo = $request->getAttribute('routeInfo');
474
        }
475
476
        if ($routeInfo[0] === Dispatcher::FOUND) {
477
            $route = $router->lookupRoute($routeInfo[1]);
478
            return $route->run($request, $response);
479
        } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
480
            if (!$this->container->has('notAllowedHandler')) {
481
                throw new MethodNotAllowedException($request, $response, $routeInfo[1]);
482
            }
483
            /** @var callable $notAllowedHandler */
484
            $notAllowedHandler = $this->container->get('notAllowedHandler');
485
            return $notAllowedHandler($request, $response, $routeInfo[1]);
486
        }
487
488
        if (!$this->container->has('notFoundHandler')) {
489
            throw new NotFoundException($request, $response);
490
        }
491
        /** @var callable $notFoundHandler */
492
        $notFoundHandler = $this->container->get('notFoundHandler');
493
        return $notFoundHandler($request, $response);
494
    }
495
496
    /**
497
     * Perform a sub-request from within an application route
498
     *
499
     * This method allows you to prepare and initiate a sub-request, run within
500
     * the context of the current request. This WILL NOT issue a remote HTTP
501
     * request. Instead, it will route the provided URL, method, headers,
502
     * cookies, body, and server variables against the set of registered
503
     * application routes. The result response object is returned.
504
     *
505
     * @param  string            $method      The request method (e.g., GET, POST, PUT, etc.)
506
     * @param  string            $path        The request URI path
507
     * @param  string            $query       The request URI query string
508
     * @param  array             $headers     The request headers (key-value array)
509
     * @param  array             $cookies     The request cookies (key-value array)
510
     * @param  string            $bodyContent The request body
511
     * @param  ResponseInterface $response     The response object (optional)
512
     * @return ResponseInterface
513
     */
514
    public function subRequest(
515
        $method,
516
        $path,
517
        $query = '',
518
        array $headers = [],
519
        array $cookies = [],
520
        $bodyContent = '',
521
        ResponseInterface $response = null
522
    ) {
523
        $env = $this->container->get('environment');
524
        $uri = Uri::createFromEnvironment($env)->withPath($path)->withQuery($query);
525
        $headers = new Headers($headers);
526
        $serverParams = $env->all();
527
        $body = new Body(fopen('php://temp', 'r+'));
528
        $body->write($bodyContent);
529
        $body->rewind();
530
        $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body);
531
532
        if (!$response) {
533
            $response = $this->container->get('response');
534
        }
535
536
        return $this($request, $response);
537
    }
538
539
    /**
540
     * Dispatch the router to find the route. Prepare the route for use.
541
     *
542
     * @param ServerRequestInterface $request
543
     * @param RouterInterface        $router
544
     * @return ServerRequestInterface
545
     */
546
    protected function dispatchRouterAndPrepareRoute(ServerRequestInterface $request, RouterInterface $router)
547
    {
548
        $routeInfo = $router->dispatch($request);
549
550
        if ($routeInfo[0] === Dispatcher::FOUND) {
551
            $routeArguments = [];
552
            foreach ($routeInfo[2] as $k => $v) {
553
                $routeArguments[$k] = urldecode($v);
554
            }
555
556
            $route = $router->lookupRoute($routeInfo[1]);
557
            $route->prepare($request, $routeArguments);
558
559
            // add route to the request's attributes in case a middleware or handler needs access to the route
560
            $request = $request->withAttribute('route', $route);
561
        }
562
563
        $routeInfo['request'] = [$request->getMethod(), (string) $request->getUri()];
564
565
        return $request->withAttribute('routeInfo', $routeInfo);
566
    }
567
568
    /**
569
     * Finalize response
570
     *
571
     * @param ResponseInterface $response
572
     * @return ResponseInterface
573
     */
574
    protected function finalize(ResponseInterface $response)
575
    {
576
        // stop PHP sending a Content-Type automatically
577
        ini_set('default_mimetype', '');
578
579
        if ($this->isEmptyResponse($response)) {
580
            return $response->withoutHeader('Content-Type')->withoutHeader('Content-Length');
581
        }
582
583
        // Add Content-Length header if `addContentLengthHeader` setting is set
584
        if (isset($this->container->get('settings')['addContentLengthHeader']) &&
585
            $this->container->get('settings')['addContentLengthHeader'] == true) {
586
            if (ob_get_length() > 0) {
587
                throw new \RuntimeException("Unexpected data in output buffer. " .
588
                    "Maybe you have characters before an opening <?php tag?");
589
            }
590
            $size = $response->getBody()->getSize();
591
            if ($size !== null && !$response->hasHeader('Content-Length')) {
592
                $response = $response->withHeader('Content-Length', (string) $size);
593
            }
594
        }
595
596
        return $response;
597
    }
598
599
    /**
600
     * Helper method, which returns true if the provided response must not output a body and false
601
     * if the response could have a body.
602
     *
603
     * @see https://tools.ietf.org/html/rfc7231
604
     *
605
     * @param ResponseInterface $response
606
     * @return bool
607
     */
608
    protected function isEmptyResponse(ResponseInterface $response)
609
    {
610
        if (method_exists($response, 'isEmpty')) {
611
            return $response->isEmpty();
612
        }
613
614
        return in_array($response->getStatusCode(), [204, 205, 304]);
615
    }
616
617
    /**
618
     * Call relevant handler from the Container if needed. If it doesn't exist,
619
     * then just re-throw.
620
     *
621
     * @param  Exception $e
622
     * @param  ServerRequestInterface $request
623
     * @param  ResponseInterface $response
624
     *
625
     * @return ResponseInterface
626
     * @throws Exception if a handler is needed and not found
627
     */
628
    protected function handleException(Exception $e, ServerRequestInterface $request, ResponseInterface $response)
629
    {
630
        if ($e instanceof MethodNotAllowedException) {
631
            $handler = 'notAllowedHandler';
632
            $params = [$e->getRequest(), $e->getResponse(), $e->getAllowedMethods()];
633
        } elseif ($e instanceof NotFoundException) {
634
            $handler = 'notFoundHandler';
635
            $params = [$e->getRequest(), $e->getResponse(), $e];
636
        } elseif ($e instanceof SlimException) {
637
            // This is a Stop exception and contains the response
638
            return $e->getResponse();
639
        } else {
640
            // Other exception, use $request and $response params
641
            $handler = 'errorHandler';
642
            $params = [$request, $response, $e];
643
        }
644
645 View Code Duplication
        if ($this->container->has($handler)) {
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...
646
            $callable = $this->container->get($handler);
647
            // Call the registered handler
648
            return call_user_func_array($callable, $params);
649
        }
650
651
        // No handlers found, so just throw the exception
652
        throw $e;
653
    }
654
655
    /**
656
     * Call relevant handler from the Container if needed. If it doesn't exist,
657
     * then just re-throw.
658
     *
659
     * @param  Throwable $e
660
     * @param  ServerRequestInterface $request
661
     * @param  ResponseInterface $response
662
     * @return ResponseInterface
663
     * @throws Throwable
664
     */
665
    protected function handlePhpError(Throwable $e, ServerRequestInterface $request, ResponseInterface $response)
666
    {
667
        $handler = 'phpErrorHandler';
668
        $params = [$request, $response, $e];
669
670 View Code Duplication
        if ($this->container->has($handler)) {
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...
671
            $callable = $this->container->get($handler);
672
            // Call the registered handler
673
            return call_user_func_array($callable, $params);
674
        }
675
676
        // No handlers found, so just throw the exception
677
        throw $e;
678
    }
679
}
680