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