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
02:15
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
        $response = $this->container->get('response');
293
294
        try {
295
            $response = $this->process($this->container->get('request'), $response);
296
        } catch (InvalidMethodException $e) {
297
            $response = $this->processInvalidMethod($e->getRequest(), $response);
298
        }
299
300
        if (!$silent) {
301
            $this->respond($response);
302
        }
303
304
        return $response;
305
    }
306
307
    /**
308
     * Pull route info for a request with a bad method to decide whether to
309
     * return a not-found error (default) or a bad-method error, then run
310
     * the handler for that error, returning the resulting response.
311
     *
312
     * Used for cases where an incoming request has an unrecognized method,
313
     * rather than throwing an exception and not catching it all the way up.
314
     *
315
     * @param ServerRequestInterface $request
316
     * @param ResponseInterface $response
317
     * @return ResponseInterface
318
     */
319
    protected function processInvalidMethod(ServerRequestInterface $request, ResponseInterface $response)
320
    {
321
        $router = $this->container->get('router');
322 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...
323
            $router->setBasePath($request->getUri()->getBasePath());
324
        }
325
326
        $request = $this->dispatchRouterAndPrepareRoute($request, $router);
327
        $routeInfo = $request->getAttribute('routeInfo', [RouterInterface::DISPATCH_STATUS => Dispatcher::NOT_FOUND]);
328
329
        if ($routeInfo[RouterInterface::DISPATCH_STATUS] === Dispatcher::METHOD_NOT_ALLOWED) {
330
            return $this->handleException(
331
                new MethodNotAllowedException($request, $response, $routeInfo[RouterInterface::ALLOWED_METHODS]),
332
                $request,
333
                $response
334
            );
335
        }
336
337
        return $this->handleException(new NotFoundException($request, $response), $request, $response);
338
    }
339
340
    /**
341
     * Process a request
342
     *
343
     * This method traverses the application middleware stack and then returns the
344
     * resultant Response object.
345
     *
346
     * @param ServerRequestInterface $request
347
     * @param ResponseInterface $response
348
     * @return ResponseInterface
349
     *
350
     * @throws Exception
351
     * @throws MethodNotAllowedException
352
     * @throws NotFoundException
353
     */
354
    public function process(ServerRequestInterface $request, ResponseInterface $response)
355
    {
356
        // Ensure basePath is set
357
        $router = $this->container->get('router');
358 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...
359
            $router->setBasePath($request->getUri()->getBasePath());
360
        }
361
362
        // Dispatch the Router first if the setting for this is on
363
        if ($this->container->get('settings')['determineRouteBeforeAppMiddleware'] === true) {
364
            // Dispatch router (note: you won't be able to alter routes after this)
365
            $request = $this->dispatchRouterAndPrepareRoute($request, $router);
366
        }
367
368
        // Traverse middleware stack
369
        try {
370
            $response = $this->callMiddlewareStack($request, $response);
371
        } catch (Exception $e) {
372
            $response = $this->handleException($e, $request, $response);
373
        } 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...
374
            $response = $this->handlePhpError($e, $request, $response);
375
        }
376
377
        $response = $this->finalize($response);
378
379
        return $response;
380
    }
381
382
    /**
383
     * Send the response the client
384
     *
385
     * @param ResponseInterface $response
386
     */
387
    public function respond(ResponseInterface $response)
388
    {
389
        // Send response
390
        if (!headers_sent()) {
391
            // Status
392
            header(sprintf(
393
                'HTTP/%s %s %s',
394
                $response->getProtocolVersion(),
395
                $response->getStatusCode(),
396
                $response->getReasonPhrase()
397
            ));
398
399
            // Headers
400
            foreach ($response->getHeaders() as $name => $values) {
401
                foreach ($values as $value) {
402
                    header(sprintf('%s: %s', $name, $value), false);
403
                }
404
            }
405
        }
406
407
        // Body
408
        if (!$this->isEmptyResponse($response)) {
409
            $body = $response->getBody();
410
            if ($body->isSeekable()) {
411
                $body->rewind();
412
            }
413
            $settings       = $this->container->get('settings');
414
            $chunkSize      = $settings['responseChunkSize'];
415
416
            $contentLength  = $response->getHeaderLine('Content-Length');
417
            if (!$contentLength) {
418
                $contentLength = $body->getSize();
419
            }
420
421
422
            if (isset($contentLength)) {
423
                $amountToRead = $contentLength;
424
                while ($amountToRead > 0 && !$body->eof()) {
425
                    $data = $body->read(min($chunkSize, $amountToRead));
426
                    echo $data;
427
428
                    $amountToRead -= strlen($data);
429
430
                    if (connection_status() != CONNECTION_NORMAL) {
431
                        break;
432
                    }
433
                }
434
            } else {
435
                while (!$body->eof()) {
436
                    echo $body->read($chunkSize);
437
                    if (connection_status() != CONNECTION_NORMAL) {
438
                        break;
439
                    }
440
                }
441
            }
442
        }
443
    }
444
445
    /**
446
     * Invoke application
447
     *
448
     * This method implements the middleware interface. It receives
449
     * Request and Response objects, and it returns a Response object
450
     * after compiling the routes registered in the Router and dispatching
451
     * the Request object to the appropriate Route callback routine.
452
     *
453
     * @param  ServerRequestInterface $request  The most recent Request object
454
     * @param  ResponseInterface      $response The most recent Response object
455
     *
456
     * @return ResponseInterface
457
     * @throws MethodNotAllowedException
458
     * @throws NotFoundException
459
     */
460
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
461
    {
462
        // Get the route info
463
        $routeInfo = $request->getAttribute('routeInfo');
464
465
        /** @var \Slim\Interfaces\RouterInterface $router */
466
        $router = $this->container->get('router');
467
468
        // If router hasn't been dispatched or the URI changed then dispatch
469
        if (null === $routeInfo || ($routeInfo['request'] !== [$request->getMethod(), (string) $request->getUri()])) {
470
            $request = $this->dispatchRouterAndPrepareRoute($request, $router);
471
            $routeInfo = $request->getAttribute('routeInfo');
472
        }
473
474
        if ($routeInfo[0] === Dispatcher::FOUND) {
475
            $route = $router->lookupRoute($routeInfo[1]);
476
            return $route->run($request, $response);
477
        } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
478
            if (!$this->container->has('notAllowedHandler')) {
479
                throw new MethodNotAllowedException($request, $response, $routeInfo[1]);
480
            }
481
            /** @var callable $notAllowedHandler */
482
            $notAllowedHandler = $this->container->get('notAllowedHandler');
483
            return $notAllowedHandler($request, $response, $routeInfo[1]);
484
        }
485
486
        if (!$this->container->has('notFoundHandler')) {
487
            throw new NotFoundException($request, $response);
488
        }
489
        /** @var callable $notFoundHandler */
490
        $notFoundHandler = $this->container->get('notFoundHandler');
491
        return $notFoundHandler($request, $response);
492
    }
493
494
    /**
495
     * Perform a sub-request from within an application route
496
     *
497
     * This method allows you to prepare and initiate a sub-request, run within
498
     * the context of the current request. This WILL NOT issue a remote HTTP
499
     * request. Instead, it will route the provided URL, method, headers,
500
     * cookies, body, and server variables against the set of registered
501
     * application routes. The result response object is returned.
502
     *
503
     * @param  string            $method      The request method (e.g., GET, POST, PUT, etc.)
504
     * @param  string            $path        The request URI path
505
     * @param  string            $query       The request URI query string
506
     * @param  array             $headers     The request headers (key-value array)
507
     * @param  array             $cookies     The request cookies (key-value array)
508
     * @param  string            $bodyContent The request body
509
     * @param  ResponseInterface $response     The response object (optional)
510
     * @return ResponseInterface
511
     */
512
    public function subRequest(
513
        $method,
514
        $path,
515
        $query = '',
516
        array $headers = [],
517
        array $cookies = [],
518
        $bodyContent = '',
519
        ResponseInterface $response = null
520
    ) {
521
        $env = $this->container->get('environment');
522
        $uri = Uri::createFromEnvironment($env)->withPath($path)->withQuery($query);
523
        $headers = new Headers($headers);
524
        $serverParams = $env->all();
525
        $body = new Body(fopen('php://temp', 'r+'));
526
        $body->write($bodyContent);
527
        $body->rewind();
528
        $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body);
529
530
        if (!$response) {
531
            $response = $this->container->get('response');
532
        }
533
534
        return $this($request, $response);
535
    }
536
537
    /**
538
     * Dispatch the router to find the route. Prepare the route for use.
539
     *
540
     * @param ServerRequestInterface $request
541
     * @param RouterInterface        $router
542
     * @return ServerRequestInterface
543
     */
544
    protected function dispatchRouterAndPrepareRoute(ServerRequestInterface $request, RouterInterface $router)
545
    {
546
        $routeInfo = $router->dispatch($request);
547
548
        if ($routeInfo[0] === Dispatcher::FOUND) {
549
            $routeArguments = [];
550
            foreach ($routeInfo[2] as $k => $v) {
551
                $routeArguments[$k] = urldecode($v);
552
            }
553
554
            $route = $router->lookupRoute($routeInfo[1]);
555
            $route->prepare($request, $routeArguments);
556
557
            // add route to the request's attributes in case a middleware or handler needs access to the route
558
            $request = $request->withAttribute('route', $route);
559
        }
560
561
        $routeInfo['request'] = [$request->getMethod(), (string) $request->getUri()];
562
563
        return $request->withAttribute('routeInfo', $routeInfo);
564
    }
565
566
    /**
567
     * Finalize response
568
     *
569
     * @param ResponseInterface $response
570
     * @return ResponseInterface
571
     */
572
    protected function finalize(ResponseInterface $response)
573
    {
574
        // stop PHP sending a Content-Type automatically
575
        ini_set('default_mimetype', '');
576
577
        if ($this->isEmptyResponse($response)) {
578
            return $response->withoutHeader('Content-Type')->withoutHeader('Content-Length');
579
        }
580
581
        // Add Content-Length header if `addContentLengthHeader` setting is set
582
        if (isset($this->container->get('settings')['addContentLengthHeader']) &&
583
            $this->container->get('settings')['addContentLengthHeader'] == true) {
584
            if (ob_get_length() > 0) {
585
                throw new \RuntimeException("Unexpected data in output buffer. " .
586
                    "Maybe you have characters before an opening <?php tag?");
587
            }
588
            $size = $response->getBody()->getSize();
589
            if ($size !== null && !$response->hasHeader('Content-Length')) {
590
                $response = $response->withHeader('Content-Length', (string) $size);
591
            }
592
        }
593
594
        return $response;
595
    }
596
597
    /**
598
     * Helper method, which returns true if the provided response must not output a body and false
599
     * if the response could have a body.
600
     *
601
     * @see https://tools.ietf.org/html/rfc7231
602
     *
603
     * @param ResponseInterface $response
604
     * @return bool
605
     */
606
    protected function isEmptyResponse(ResponseInterface $response)
607
    {
608
        if (method_exists($response, 'isEmpty')) {
609
            return $response->isEmpty();
610
        }
611
612
        return in_array($response->getStatusCode(), [204, 205, 304]);
613
    }
614
615
    /**
616
     * Call relevant handler from the Container if needed. If it doesn't exist,
617
     * then just re-throw.
618
     *
619
     * @param  Exception $e
620
     * @param  ServerRequestInterface $request
621
     * @param  ResponseInterface $response
622
     *
623
     * @return ResponseInterface
624
     * @throws Exception if a handler is needed and not found
625
     */
626
    protected function handleException(Exception $e, ServerRequestInterface $request, ResponseInterface $response)
627
    {
628
        if ($e instanceof MethodNotAllowedException) {
629
            $handler = 'notAllowedHandler';
630
            $params = [$e->getRequest(), $e->getResponse(), $e->getAllowedMethods()];
631
        } elseif ($e instanceof NotFoundException) {
632
            $handler = 'notFoundHandler';
633
            $params = [$e->getRequest(), $e->getResponse(), $e];
634
        } elseif ($e instanceof SlimException) {
635
            // This is a Stop exception and contains the response
636
            return $e->getResponse();
637
        } else {
638
            // Other exception, use $request and $response params
639
            $handler = 'errorHandler';
640
            $params = [$request, $response, $e];
641
        }
642
643 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...
644
            $callable = $this->container->get($handler);
645
            // Call the registered handler
646
            return call_user_func_array($callable, $params);
647
        }
648
649
        // No handlers found, so just throw the exception
650
        throw $e;
651
    }
652
653
    /**
654
     * Call relevant handler from the Container if needed. If it doesn't exist,
655
     * then just re-throw.
656
     *
657
     * @param  Throwable $e
658
     * @param  ServerRequestInterface $request
659
     * @param  ResponseInterface $response
660
     * @return ResponseInterface
661
     * @throws Throwable
662
     */
663
    protected function handlePhpError(Throwable $e, ServerRequestInterface $request, ResponseInterface $response)
664
    {
665
        $handler = 'phpErrorHandler';
666
        $params = [$request, $response, $e];
667
668 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...
669
            $callable = $this->container->get($handler);
670
            // Call the registered handler
671
            return call_user_func_array($callable, $params);
672
        }
673
674
        // No handlers found, so just throw the exception
675
        throw $e;
676
    }
677
}
678