ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
App.php
Go to the documentation of this file.
1<?php
9namespace Slim;
10
11use Exception;
14use Throwable;
15use Closure;
16use InvalidArgumentException;
33
46class App
47{
49
55 const VERSION = '3.11.0';
56
62 private $container;
63
64 /********************************************************************************
65 * Constructor
66 *******************************************************************************/
67
74 public function __construct($container = [])
75 {
76 if (is_array($container)) {
78 }
79 if (!$container instanceof ContainerInterface) {
80 throw new InvalidArgumentException('Expected a ContainerInterface');
81 }
82 $this->container = $container;
83 }
84
90 public function getContainer()
91 {
92 return $this->container;
93 }
94
104 public function add($callable)
105 {
106 return $this->addMiddleware(new DeferredCallable($callable, $this->container));
107 }
108
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
141 public function get($pattern, $callable)
142 {
143 return $this->map(['GET'], $pattern, $callable);
144 }
145
154 public function post($pattern, $callable)
155 {
156 return $this->map(['POST'], $pattern, $callable);
157 }
158
167 public function put($pattern, $callable)
168 {
169 return $this->map(['PUT'], $pattern, $callable);
170 }
171
180 public function patch($pattern, $callable)
181 {
182 return $this->map(['PATCH'], $pattern, $callable);
183 }
184
193 public function delete($pattern, $callable)
194 {
195 return $this->map(['DELETE'], $pattern, $callable);
196 }
197
206 public function options($pattern, $callable)
207 {
208 return $this->map(['OPTIONS'], $pattern, $callable);
209 }
210
219 public function any($pattern, $callable)
220 {
221 return $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $pattern, $callable);
222 }
223
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
260 public function redirect($from, $to, $status = 302)
261 {
262 $handler = function ($request, ResponseInterface $response) use ($to, $status) {
263 return $response->withHeader('Location', (string)$to)->withStatus($status);
264 };
265
266 return $this->get($from, $handler);
267 }
268
281 public function group($pattern, $callable)
282 {
284 $group = $this->container->get('router')->pushGroup($pattern, $callable);
285 $group->setContainer($this->container);
286 $group($this);
287 $this->container->get('router')->popGroup();
288 return $group;
289 }
290
291 /********************************************************************************
292 * Runner
293 *******************************************************************************/
294
308 public function run($silent = false)
309 {
310 $response = $this->container->get('response');
311
312 try {
313 ob_start();
314 $response = $this->process($this->container->get('request'), $response);
315 } catch (InvalidMethodException $e) {
316 $response = $this->processInvalidMethod($e->getRequest(), $response);
317 } finally {
318 $output = ob_get_clean();
319 }
320
321 if (!empty($output) && $response->getBody()->isWritable()) {
322 $outputBuffering = $this->container->get('settings')['outputBuffering'];
323 if ($outputBuffering === 'prepend') {
324 // prepend output buffer content
325 $body = new Http\Body(fopen('php://temp', 'r+'));
326 $body->write($output . $response->getBody());
327 $response = $response->withBody($body);
328 } elseif ($outputBuffering === 'append') {
329 // append output buffer content
330 $response->getBody()->write($output);
331 }
332 }
333
334 $response = $this->finalize($response);
335
336 if (!$silent) {
337 $this->respond($response);
338 }
339
340 return $response;
341 }
342
355 protected function processInvalidMethod(ServerRequestInterface $request, ResponseInterface $response)
356 {
357 $router = $this->container->get('router');
358 if (is_callable([$request->getUri(), 'getBasePath']) && is_callable([$router, 'setBasePath'])) {
359 $router->setBasePath($request->getUri()->getBasePath());
360 }
361
362 $request = $this->dispatchRouterAndPrepareRoute($request, $router);
363 $routeInfo = $request->getAttribute('routeInfo', [RouterInterface::DISPATCH_STATUS => Dispatcher::NOT_FOUND]);
364
365 if ($routeInfo[RouterInterface::DISPATCH_STATUS] === Dispatcher::METHOD_NOT_ALLOWED) {
366 return $this->handleException(
367 new MethodNotAllowedException($request, $response, $routeInfo[RouterInterface::ALLOWED_METHODS]),
368 $request,
370 );
371 }
372
373 return $this->handleException(new NotFoundException($request, $response), $request, $response);
374 }
375
390 public function process(ServerRequestInterface $request, ResponseInterface $response)
391 {
392 // Ensure basePath is set
393 $router = $this->container->get('router');
394 if (is_callable([$request->getUri(), 'getBasePath']) && is_callable([$router, 'setBasePath'])) {
395 $router->setBasePath($request->getUri()->getBasePath());
396 }
397
398 // Dispatch the Router first if the setting for this is on
399 if ($this->container->get('settings')['determineRouteBeforeAppMiddleware'] === true) {
400 // Dispatch router (note: you won't be able to alter routes after this)
401 $request = $this->dispatchRouterAndPrepareRoute($request, $router);
402 }
403
404 // Traverse middleware stack
405 try {
406 $response = $this->callMiddlewareStack($request, $response);
407 } catch (Exception $e) {
408 $response = $this->handleException($e, $request, $response);
409 } catch (Throwable $e) {
410 $response = $this->handlePhpError($e, $request, $response);
411 }
412
413 return $response;
414 }
415
421 public function respond(ResponseInterface $response)
422 {
423 // Send response
424 if (!headers_sent()) {
425 // Headers
426 foreach ($response->getHeaders() as $name => $values) {
427 $first = stripos($name, 'Set-Cookie') === 0 ? false : true;
428 foreach ($values as $value) {
429 header(sprintf('%s: %s', $name, $value), $first);
430 $first = false;
431 }
432 }
433
434 // Set the status _after_ the headers, because of PHP's "helpful" behavior with location headers.
435 // See https://github.com/slimphp/Slim/issues/1730
436
437 // Status
438 header(sprintf(
439 'HTTP/%s %s %s',
440 $response->getProtocolVersion(),
441 $response->getStatusCode(),
442 $response->getReasonPhrase()
443 ), true, $response->getStatusCode());
444 }
445
446 // Body
447 if (!$this->isEmptyResponse($response)) {
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($chunkSize, $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($chunkSize);
476 if (connection_status() != CONNECTION_NORMAL) {
477 break;
478 }
479 }
480 }
481 }
482 }
483
499 public function __invoke(ServerRequestInterface $request, ResponseInterface $response)
500 {
501 // Get the route info
502 $routeInfo = $request->getAttribute('routeInfo');
503
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 }
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 }
529 $notFoundHandler = $this->container->get('notFoundHandler');
530 return $notFoundHandler($request, $response);
531 }
532
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
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
611 protected function finalize(ResponseInterface $response)
612 {
613 // stop PHP sending a Content-Type automatically
614 ini_set('default_mimetype', '');
615
616 if ($this->isEmptyResponse($response)) {
617 return $response->withoutHeader('Content-Type')->withoutHeader('Content-Length');
618 }
619
620 // Add Content-Length header if `addContentLengthHeader` setting is set
621 if (isset($this->container->get('settings')['addContentLengthHeader']) &&
622 $this->container->get('settings')['addContentLengthHeader'] == true) {
623 if (ob_get_length() > 0) {
624 throw new \RuntimeException("Unexpected data in output buffer. " .
625 "Maybe you have characters before an opening <?php tag?");
626 }
627 $size = $response->getBody()->getSize();
628 if ($size !== null && !$response->hasHeader('Content-Length')) {
629 $response = $response->withHeader('Content-Length', (string) $size);
630 }
631 }
632
633 return $response;
634 }
635
645 protected function isEmptyResponse(ResponseInterface $response)
646 {
647 if (method_exists($response, 'isEmpty')) {
648 return $response->isEmpty();
649 }
650
651 return in_array($response->getStatusCode(), [204, 205, 304]);
652 }
653
665 protected function handleException(Exception $e, ServerRequestInterface $request, ResponseInterface $response)
666 {
667 if ($e instanceof MethodNotAllowedException) {
668 $handler = 'notAllowedHandler';
669 $params = [$e->getRequest(), $e->getResponse(), $e->getAllowedMethods()];
670 } elseif ($e instanceof NotFoundException) {
671 $handler = 'notFoundHandler';
672 $params = [$e->getRequest(), $e->getResponse(), $e];
673 } elseif ($e instanceof SlimException) {
674 // This is a Stop exception and contains the response
675 return $e->getResponse();
676 } else {
677 // Other exception, use $request and $response params
678 $handler = 'errorHandler';
679 $params = [$request, $response, $e];
680 }
681
682 if ($this->container->has($handler)) {
683 $callable = $this->container->get($handler);
684 // Call the registered handler
685 return call_user_func_array($callable, $params);
686 }
687
688 // No handlers found, so just throw the exception
689 throw $e;
690 }
691
702 protected function handlePhpError(Throwable $e, ServerRequestInterface $request, ResponseInterface $response)
703 {
704 $handler = 'phpErrorHandler';
705 $params = [$request, $response, $e];
706
707 if ($this->container->has($handler)) {
708 $callable = $this->container->get($handler);
709 // Call the registered handler
710 return call_user_func_array($callable, $params);
711 }
712
713 // No handlers found, so just throw the exception
714 throw $e;
715 }
716}
sprintf('%.4f', $callTime)
$env
$size
Definition: RandomTest.php:84
add()
Definition: add.php:2
An exception for terminatinating execution or to throw for unit testing.
Body.
Definition: Body.php:20
Value object representing a URI.
Definition: Uri.php:36
if(!is_dir( $entity_dir)) exit("Fatal Error ([A-Za-z0-9]+)\s+" &#(? foreach( $entity_files as $file) $output
Describes the interface of a container that exposes methods to read its entries.
Representation of an outgoing, client-side request.
Representation of an outgoing, server-side response.
Representation of an incoming, server-side HTTP request.
if($format !==null) $name
Definition: metadata.php:146
if(preg_match('#\.( $contentLength[^/\.]+) $#D', $path, $type)) if($contentType===null)
Definition: module.php:163
__call($method, array $arguments)
Plugins pass-through.
Slim Framework (https://slimframework.com)
Definition: App.php:9
trait MiddlewareAwareTrait
Middleware.
addMiddleware(callable $callable)
Add middleware.
$query
$response
$handler
$from
$container
Definition: wac.php:13
$params
Definition: disable.php:11