ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
App.php
Go to the documentation of this file.
1 <?php
9 namespace Slim;
10 
11 use Exception;
14 use Throwable;
15 use Closure;
25 use Slim\Http\Uri;
27 use Slim\Http\Body;
33 
46 class 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 
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,
369  $response
370  );
371  }
372 
373  return $this->handleException(new NotFoundException($request, $response), $request, $response);
374  }
375 
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 
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 
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 
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 }
getMethod()
Retrieves the HTTP method of the request.
$container
Definition: App.php:62
map(array $methods, $pattern, $callable)
Add route with multiple methods.
Definition: App.php:233
getProtocolVersion()
Retrieves the HTTP protocol version as a string.
$path
Definition: aliased.php:25
Definition: App.php:46
patch($pattern, $callable)
Add PATCH route.
Definition: App.php:180
$size
Definition: RandomTest.php:84
any($pattern, $callable)
Add route for any HTTP method.
Definition: App.php:219
Representation of an incoming, server-side HTTP request.
process(ServerRequestInterface $request, ResponseInterface $response)
Process a request.
Definition: App.php:390
withoutHeader($name)
Return an instance without the specified header.
trait MiddlewareAwareTrait
Middleware.
put($pattern, $callable)
Add PUT route.
Definition: App.php:167
getReasonPhrase()
Gets the response reason phrase associated with the status code.
foreach($paths as $path) $request
Definition: asyncclient.php:32
Request.
Definition: Request.php:33
handleException(Exception $e, ServerRequestInterface $request, ResponseInterface $response)
Call relevant handler from the Container if needed.
Definition: App.php:665
$from
run($silent=false)
Run application.
Definition: App.php:308
post($pattern, $callable)
Add POST route.
Definition: App.php:154
withHeader($name, $value)
Return an instance with the provided value replacing the specified header.
getStatusCode()
Gets the response status code.
$container
Definition: wac.php:13
dispatchRouterAndPrepareRoute(ServerRequestInterface $request, RouterInterface $router)
Dispatch the router to find the route.
Definition: App.php:583
Describes the interface of a container that exposes methods to read its entries.
$env
__call($method, $args)
Calling a non-existant method on App checks to see if there&#39;s an item in the container that is callab...
Definition: App.php:117
getUri()
Retrieves the URI instance.
if(preg_match('#\.( $contentLength[^/\.]+)$#D', $path, $type)) if($contentType===null)
Definition: module.php:165
options($pattern, $callable)
Add OPTIONS route.
Definition: App.php:206
Headers.
Definition: Headers.php:27
addMiddleware(callable $callable)
Add middleware.
$values
getContainer()
Enable access to the DI container by consumers of $app.
Definition: App.php:90
getBody()
Gets the body of the message.
add($callable)
Add middleware.
Definition: App.php:104
Representation of an outgoing, server-side response.
withAttribute($name, $value)
Return an instance with the specified derived request attribute.
$query
__construct($container=[])
Create new application.
Definition: App.php:74
getAttribute($name, $default=null)
Retrieve a single derived request attribute.
isEmptyResponse(ResponseInterface $response)
Helper method, which returns true if the provided response must not output a body and false if the re...
Definition: App.php:645
finalize(ResponseInterface $response)
Finalize response.
Definition: App.php:611
respond(ResponseInterface $response)
Send the response to the client.
Definition: App.php:421
Slim Framework (https://slimframework.com)
Definition: App.php:9
subRequest( $method, $path, $query='', array $headers=[], array $cookies=[], $bodyContent='', ResponseInterface $response=null)
Perform a sub-request from within an application route.
Definition: App.php:551
Body.
Definition: Body.php:19
hasHeader($name)
Checks if a header exists by the given case-insensitive name.
processInvalidMethod(ServerRequestInterface $request, ResponseInterface $response)
Pull route info for a request with a bad method to decide whether to return a not-found error (defaul...
Definition: App.php:355
redirect($from, $to, $status=302)
Add a route that sends an HTTP redirect.
Definition: App.php:260
$response
handlePhpError(Throwable $e, ServerRequestInterface $request, ResponseInterface $response)
Call relevant handler from the Container if needed.
Definition: App.php:702
$handler
getHeaders()
Retrieves all message header values.
getHeaderLine($name)
Retrieves a comma-separated string of the values for a single header.
dispatch(ServerRequestInterface $request)
Dispatch router for HTTP request.
$data
Definition: bench.php:6