routes = $routes; $this->container = $container; $this->invoker = $this->createDefaultInvoker(); } private function iterateRoutes() { yield from $this->iterateRoutesFromArray($this->routes); } private function iterateRoutesFromArray($routeArr, $prefix = '', $known = [], $middleware = []) { $route = $routeArr['middleware'] ?? []; $middleware = array_merge($middleware, array_map(function ($route) { if (!$route instanceof Handle) { return new Handle($route); } return $route; }, (array)$route)); foreach ($routeArr as $key => $route) { if ($key === 'middleware') { continue; } if (is_array($route) && !(isset($route[0]) && isset($route[1]))) { yield from $this->iterateRoutesFromArray($route, $prefix . $key, $known, $middleware); continue; } if (!$route instanceof Handle) { $route = new Handle($route); } $route = $route->withPrependedMiddleware($middleware); if ($key[0] !== '/') { yield [$key, $prefix, $route]; continue; } yield ['GET', $prefix . $key, $route]; } } public function buildRoutes() { $this->router = simpleDispatcher(function (RouteCollector $collector) { foreach ($this->iterateRoutes() as $route) { $collector->addRoute(...$route); } }); } public function serve() { $request = ServerRequest::fromGlobals(); $response = $this->respond($request); header('HTTP/' . $response->getProtocolVersion() . ' ' . $response->getStatusCode() . ' ' . $response->getReasonPhrase()); foreach ($response->getHeaders() as $headerName => $headerValues) { $first = true; foreach ($headerValues as $headerValue) { header($headerName . ': ' . $headerValue, $first === true); $first = false; } } $stdout = fopen('php://output', 'w+'); copy_to_stream($response->getBody(), stream_for($stdout)); } public function respond(ServerRequestInterface $request, ResponseInterface $response = null): ResponseInterface { $path = $request->getUri()->getPath(); $method = $request->getMethod(); $routeInfo = $this->router->dispatch($method, $path); switch ($routeInfo[0]) { case Dispatcher::NOT_FOUND: // todo: Error page break; case Dispatcher::METHOD_NOT_ALLOWED: // todo: Error page break; case Dispatcher::FOUND: [$_, $handler, $vars] = $routeInfo; $response = $this->handleHttp($request, $handler, $vars, $response); break; } if ($response === null) { $response = (new Response()) ->withStatus(200); } return $response; } private function createDefaultInvoker() { return new Invoker(new ResolverChain(array( new NumericArrayResolver(), new AssociativeArrayResolver(), new DefaultValueResolver(), new TypeHintResolver(), new TypeHintContainerResolver($this->container), )), $this->container); } public function handleHttp(ServerRequestInterface $request, Handle $handler, array $vars, ResponseInterface $response = null): ResponseInterface { $middlewareArray = $handler->getMiddleware(); $middlewarePointer = 0; $next = null; if ($response === null) { $response = new Response(200, [], stream_for('Empty response')); } $context = new Context($request, $response, $vars); $next = function () use ($context, $handler, $middlewareArray, &$middlewarePointer, &$next) { $output = null; if (count($middlewareArray) > $middlewarePointer) { $handler = $middlewareArray[$middlewarePointer++]; } $call = $handler->getCall(); if (is_array($call)) { $obj = $this->container->get($call[0]); $call = [$obj, $call[1]]; } $context->setSettings($handler->getSettings()); $output = $this->invoker->call($call, [ 'request' => $context->getRequest(), ServerRequestInterface::class => $context->getRequest(), 'response' => $context->getResponse(), ResponseInterface::class => $context->getResponse(), 'context' => $context, Context::class => $context, 'vars' => $context->getVars(), 'settings' => $context->getSettings(), 'next' => $next, ]); $this->normalizeResponse($output, $context); }; $next(); return $context->renderResponse(); } private function normalizeResponse($response, Context $context) { if ($response === null) { return; } if ($response instanceof ResponseInterface) { $context->setResponse($response); return; } $body = stream_for($response); $context->withResponse(function (ResponseInterface $response) use ($body) { return $response->withBody($body); }); } }