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 (#2701)
by Theodore
02:00
created

App::processInvalidMethod()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20

Duplication

Lines 3
Ratio 15 %

Importance

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