container = $container; } /** * Enable access to the DI container by consumers of $app * * @return ContainerInterface */ public function getContainer() { return $this->container; } /** * Add middleware * * This method prepends new middleware to the app's middleware stack. * * @param mixed $callable The callback routine * * @return static */ public function add($callable) { $callable = $this->resolveCallable($callable); if ($callable instanceof Closure) { $callable = $callable->bindTo($this->container); } return $this->addMiddleware($callable); } /** * Calling a non-existant method on App checks to see if there's an item * in the container than is callable and if so, calls it. * * @param string $method * @param array $args * @return mixed */ public function __call($method, $args) { if ($this->container->has($method)) { $obj = $this->container->get($method); if (is_callable($obj)) { return call_user_func_array($obj, $args); } } } /******************************************************************************** * Router proxy methods *******************************************************************************/ /** * Add GET route * * @param string $pattern The route URI pattern * @param mixed $callable The route callback routine * * @return \Slim\Interfaces\RouteInterface */ public function get($pattern, $callable) { return $this->map(['GET'], $pattern, $callable); } /** * Add POST route * * @param string $pattern The route URI pattern * @param mixed $callable The route callback routine * * @return \Slim\Interfaces\RouteInterface */ public function post($pattern, $callable) { return $this->map(['POST'], $pattern, $callable); } /** * Add PUT route * * @param string $pattern The route URI pattern * @param mixed $callable The route callback routine * * @return \Slim\Interfaces\RouteInterface */ public function put($pattern, $callable) { return $this->map(['PUT'], $pattern, $callable); } /** * Add PATCH route * * @param string $pattern The route URI pattern * @param mixed $callable The route callback routine * * @return \Slim\Interfaces\RouteInterface */ public function patch($pattern, $callable) { return $this->map(['PATCH'], $pattern, $callable); } /** * Add DELETE route * * @param string $pattern The route URI pattern * @param mixed $callable The route callback routine * * @return \Slim\Interfaces\RouteInterface */ public function delete($pattern, $callable) { return $this->map(['DELETE'], $pattern, $callable); } /** * Add OPTIONS route * * @param string $pattern The route URI pattern * @param mixed $callable The route callback routine * * @return \Slim\Interfaces\RouteInterface */ public function options($pattern, $callable) { return $this->map(['OPTIONS'], $pattern, $callable); } /** * Add route for any HTTP method * * @param string $pattern The route URI pattern * @param mixed $callable The route callback routine * * @return \Slim\Interfaces\RouteInterface */ public function any($pattern, $callable) { return $this->map(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], $pattern, $callable); } /** * Add route with multiple methods * * @param string[] $methods Numeric array of HTTP method names * @param string $pattern The route URI pattern * @param mixed $callable The route callback routine * * @return RouteInterface */ public function map(array $methods, $pattern, $callable) { if ($callable instanceof Closure) { $callable = $callable->bindTo($this->container); } $route = $this->container->get('router')->map($methods, $pattern, $callable); if (is_callable([$route, 'setContainer'])) { $route->setContainer($this->container); } if (is_callable([$route, 'setOutputBuffering'])) { $route->setOutputBuffering($this->container->get('settings')['outputBuffering']); } return $route; } /** * Route Groups * * This method accepts a route pattern and a callback. All route * declarations in the callback will be prepended by the group(s) * that it is in. * * @param string $pattern * @param callable $callable * * @return RouteGroupInterface */ public function group($pattern, $callable) { /** @var RouteGroup $group */ $group = $this->container->get('router')->pushGroup($pattern, $callable); $group->setContainer($this->container); $group($this); $this->container->get('router')->popGroup(); return $group; } /******************************************************************************** * Runner *******************************************************************************/ /** * Run application * * This method traverses the application middleware stack and then sends the * resultant Response object to the HTTP client. * * @param bool|false $silent * @return ResponseInterface * * @throws Exception * @throws MethodNotAllowedException * @throws NotFoundException */ public function run($silent = false) { $request = $this->container->get('request'); $response = $this->container->get('response'); // Ensure basePath is set $router = $this->container->get('router'); if (is_callable([$request->getUri(), 'getBasePath']) && is_callable([$router, 'setBasePath'])) { $router->setBasePath($request->getUri()->getBasePath()); } // Dispatch the Router first if the setting for this is on if ($this->container->get('settings')['determineRouteBeforeAppMiddleware'] === true) { // Dispatch router (note: you won't be able to alter routes after this) $request = $this->dispatchRouterAndPrepareRoute($request, $router); } // Traverse middleware stack try { $response = $this->callMiddlewareStack($request, $response); } catch (MethodNotAllowedException $e) { if (!$this->container->has('notAllowedHandler')) { throw $e; } /** @var callable $notAllowedHandler */ $notAllowedHandler = $this->container->get('notAllowedHandler'); $response = $notAllowedHandler($e->getRequest(), $e->getResponse(), $e->getAllowedMethods()); } catch (NotFoundException $e) { if (!$this->container->has('notFoundHandler')) { throw $e; } /** @var callable $notFoundHandler */ $notFoundHandler = $this->container->get('notFoundHandler'); $response = $notFoundHandler($e->getRequest(), $e->getResponse()); } catch (SlimException $e) { $response = $e->getResponse(); } catch (Exception $e) { if (!$this->container->has('errorHandler')) { throw $e; } /** @var callable $errorHandler */ $errorHandler = $this->container->get('errorHandler'); $response = $errorHandler($request, $response, $e); } $response = $this->finalize($response); if (!$silent) { $this->respond($response); } return $response; } /** * Send the response the client * * @param ResponseInterface $response */ public function respond(ResponseInterface $response) { // Send response if (!headers_sent()) { // Status header(sprintf( 'HTTP/%s %s %s', $response->getProtocolVersion(), $response->getStatusCode(), $response->getReasonPhrase() )); // Headers foreach ($response->getHeaders() as $name => $values) { foreach ($values as $value) { header(sprintf('%s: %s', $name, $value), false); } } } // Body if (!$this->isEmptyResponse($response)) { $body = $response->getBody(); if ($body->isSeekable()) { $body->rewind(); } $settings = $this->container->get('settings'); $chunkSize = $settings['responseChunkSize']; $contentLength = $response->getHeaderLine('Content-Length'); if (!$contentLength) { $contentLength = $body->getSize(); } $totalChunks = ceil($contentLength / $chunkSize); $lastChunkSize = $contentLength % $chunkSize; $currentChunk = 0; while (!$body->eof() && $currentChunk < $totalChunks) { if (++$currentChunk == $totalChunks && $lastChunkSize > 0) { $chunkSize = $lastChunkSize; } echo $body->read($chunkSize); if (connection_status() != CONNECTION_NORMAL) { break; } } } } /** * Invoke application * * This method implements the middleware interface. It receives * Request and Response objects, and it returns a Response object * after compiling the routes registered in the Router and dispatching * the Request object to the appropriate Route callback routine. * * @param ServerRequestInterface $request The most recent Request object * @param ResponseInterface $response The most recent Response object * * @return ResponseInterface * @throws MethodNotAllowedException * @throws NotFoundException */ public function __invoke(ServerRequestInterface $request, ResponseInterface $response) { // Get the route info $routeInfo = $request->getAttribute('routeInfo'); /** @var \Slim\Interfaces\RouterInterface $router */ $router = $this->container->get('router'); // If router hasn't been dispatched or the URI changed then dispatch if (null === $routeInfo || ($routeInfo['request'] !== [$request->getMethod(), (string) $request->getUri()])) { $request = $this->dispatchRouterAndPrepareRoute($request, $router); $routeInfo = $request->getAttribute('routeInfo'); } if ($routeInfo[0] === Dispatcher::FOUND) { $route = $router->lookupRoute($routeInfo[1]); return $route->run($request, $response); } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { if (!$this->container->has('notAllowedHandler')) { throw new MethodNotAllowedException($request, $response, $routeInfo[1]); } /** @var callable $notAllowedHandler */ $notAllowedHandler = $this->container->get('notAllowedHandler'); return $notAllowedHandler($request, $response, $routeInfo[1]); } if (!$this->container->has('notFoundHandler')) { throw new NotFoundException($request, $response); } /** @var callable $notFoundHandler */ $notFoundHandler = $this->container->get('notFoundHandler'); return $notFoundHandler($request, $response); } /** * Perform a sub-request from within an application route * * This method allows you to prepare and initiate a sub-request, run within * the context of the current request. This WILL NOT issue a remote HTTP * request. Instead, it will route the provided URL, method, headers, * cookies, body, and server variables against the set of registered * application routes. The result response object is returned. * * @param string $method The request method (e.g., GET, POST, PUT, etc.) * @param string $path The request URI path * @param string $query The request URI query string * @param array $headers The request headers (key-value array) * @param array $cookies The request cookies (key-value array) * @param string $bodyContent The request body * @param ResponseInterface $response The response object (optional) * @return ResponseInterface */ public function subRequest($method, $path, $query = '', array $headers = [], array $cookies = [], $bodyContent = '', ResponseInterface $response = null) { $env = $this->container->get('environment'); $uri = Uri::createFromEnvironment($env)->withPath($path)->withQuery($query); $headers = new Headers($headers); $serverParams = $env->all(); $body = new Body(fopen('php://temp', 'r+')); $body->write($bodyContent); $body->rewind(); $request = new Request($method, $uri, $headers, $cookies, $serverParams, $body); if (!$response) { $response = $this->container->get('response'); } return $this($request, $response); } /** * Dispatch the router to find the route. Prepare the route for use. * * @param ServerRequestInterface $request * @param RouterInterface $router * @return ServerRequestInterface */ protected function dispatchRouterAndPrepareRoute(ServerRequestInterface $request, RouterInterface $router) { $routeInfo = $router->dispatch($request); if ($routeInfo[0] === Dispatcher::FOUND) { $routeArguments = []; foreach ($routeInfo[2] as $k => $v) { $routeArguments[$k] = urldecode($v); } $route = $router->lookupRoute($routeInfo[1]); $route->prepare($request, $routeArguments); // add route to the request's attributes in case a middleware or handler needs access to the route $request = $request->withAttribute('route', $route); } $routeInfo['request'] = [$request->getMethod(), (string) $request->getUri()]; return $request->withAttribute('routeInfo', $routeInfo); } /** * Finalize response * * @param ResponseInterface $response * @return ResponseInterface */ protected function finalize(ResponseInterface $response) { // stop PHP sending a Content-Type automatically ini_set('default_mimetype', ''); if ($this->isEmptyResponse($response)) { return $response->withoutHeader('Content-Type')->withoutHeader('Content-Length'); } $size = $response->getBody()->getSize(); if ($size !== null && !$response->hasHeader('Content-Length')) { $response = $response->withHeader('Content-Length', (string) $size); } return $response; } /** * Helper method, which returns true if the provided response must not output a body and false * if the response could have a body. * * @see https://tools.ietf.org/html/rfc7231 * * @param ResponseInterface $response * @return bool */ protected function isEmptyResponse(ResponseInterface $response) { if (method_exists($response, 'isEmpty')) { return $response->isEmpty(); } return in_array($response->getStatusCode(), [204, 205, 304]); } }