router = $router ?? new Router; $this->addObjectHandler( fn(object $object) => $object instanceof Stream, function(HttpResponseBuilder $responseBuilder, object $object) { if(!$responseBuilder->hasContentType()) $responseBuilder->setTypeStream(); $responseBuilder->setContent(new StreamContent($object)); } ); $this->addObjectHandler( fn(object $object) => $object instanceof JsonSerializable || $object instanceof stdClass, function(HttpResponseBuilder $responseBuilder, object $object) { if(!$responseBuilder->hasContentType()) $responseBuilder->setTypeJson(); $responseBuilder->setContent(new JsonContent($object)); } ); $this->addObjectHandler( fn(object $object) => $object instanceof IBencodeSerialisable, function(HttpResponseBuilder $responseBuilder, object $object) { if(!$responseBuilder->hasContentType()) $responseBuilder->setTypePlain(); $responseBuilder->setContent(new BencodedContent($object)); } ); } public function getRouter(): Router { return $this->router; } public function addObjectHandler(callable $match, callable $handler): void { $this->objectHandlers[] = [$match, $handler]; } public function addErrorHandler(int $code, callable $handler): void { $this->errorHandlers[$code] = $handler; } public function setDefaultErrorHandler(callable $handler): void { $this->defaultErrorHandler = $handler; } public function restoreDefaultErrorHandler(): void { $this->defaultErrorHandler = [self::class, 'defaultErrorHandler']; } public function dispatch(?HttpRequest $request = null, array $args = []): void { $request ??= HttpRequest::fromRequest(); $responseBuilder = new HttpResponseBuilder; $handlers = null; try { $handlers = $this->router->resolve($request->getMethod(), $request->getPath(), array_merge([ $responseBuilder, $request, ], $args)); } catch(RoutePathNotFoundException $ex) { $statusCode = 404; } catch(RouteMethodNotSupportedException $ex) { $statusCode = 405; } catch(Exception $ex) { if(Environment::isDebug()) throw $ex; } if($handlers === null) { $this->errorPage($responseBuilder, $request, $statusCode ?? 500); } else { $result = $handlers->run(); if(is_int($result)) { if(!$responseBuilder->hasStatusCode() && $result >= 100 && $result < 600) { $this->errorPage($responseBuilder, $request, $result); } elseif(!$responseBuilder->hasContent()) { $responseBuilder->setContent(new StringContent((string)$result)); } } elseif(!$responseBuilder->hasContent()) { if(is_array($result)) { if(!$responseBuilder->hasContentType()) $responseBuilder->setTypeJson(); $responseBuilder->setContent(new JsonContent($result)); } elseif(is_object($result) && !($result instanceof IString)) { foreach($this->objectHandlers as $info) if($info[0]($result)) { $info[1]($responseBuilder, $result); break; } } if(!$responseBuilder->hasContent() && $result !== null) { $charset = ($result instanceof WString) ? $result->getEncoding() : null; $result = (string)$result; $responseBuilder->setContent(new StringContent($result)); if(!$responseBuilder->hasContentType()) { if(strtolower(substr($result, 0, 5)) === 'setTypeXML($charset ?? WString::getDefaultEncoding()); elseif(strtolower(substr($result, 0, 14)) === 'setTypeHTML($charset ?? WString::getDefaultEncoding()); else $responseBuilder->setTypePlain($charset ?? 'us-ascii'); } } } } self::output($responseBuilder->toResponse()); } public static function defaultErrorHandler( HttpResponseBuilder $responseBuilder, HttpRequest $request, int $code, string $message ): void { $responseBuilder->setTypeHTML(); $responseBuilder->setContent(new StringContent(sprintf( '%1$03d %2$s

%1$03d %2$s


Index
', $code, $message, WString::getDefaultEncoding() ))); } public function errorPage( HttpResponseBuilder $responseBuilder, HttpRequest $request, int $statusCode ): void { $responseBuilder->setStatusCode($statusCode); $responseBuilder->clearStatusText(); if(!$responseBuilder->hasContent()) ($this->errorHandlers[$statusCode] ?? $this->defaultErrorHandler)( $responseBuilder, $request, $responseBuilder->getStatusCode(), $responseBuilder->getStatusText() ); } public static function output(HttpResponse $response): void { $version = $response->getHttpVersion(); header(sprintf( 'HTTP/%d.%d %03d %s', $version->getMajor(), $version->getMinor(), $response->getStatusCode(), $response->getStatusText() )); $headers = $response->getHeaders(); foreach($headers as $header) { $name = (string)$header->getName(); $lines = $header->getLines(); foreach($lines as $line) header(sprintf('%s: %s', $name, (string)$line)); } if($response->hasContent()) echo (string)$response->getContent(); } /** * Apply middleware functions to a path. * * @param string $path Path to apply the middleware to. * @param callable $handler Middleware function. */ public function use(string $path, callable $handler): void { $this->router->use($path, $handler); } /** * Merges another router with this one with possibility of changing its root. * * @param string $path Base path to use. * @param Router $router Router object to inherit from. */ public function merge(string $path, Router $router): void { $this->router->merge($path, $router); } /** * Adds a new route. * * @param string $method Request method. * @param string $path Request path. * @param callable $handler Request handler. */ public function add(string $method, string $path, callable $handler): void { $this->router->add($method, $path, $handler); } /** * Adds a new GET route. * * @param string $path Request path. * @param callable $handler Request handler. */ public function get(string $path, callable $handler): void { $this->router->add('get', $path, $handler); } /** * Adds a new POST route. * * @param string $path Request path. * @param callable $handler Request handler. */ public function post(string $path, callable $handler): void { $this->router->add('post', $path, $handler); } /** * Adds a new DELETE route. * * @param string $path Request path. * @param callable $handler Request handler. */ public function delete(string $path, callable $handler): void { $this->router->add('delete', $path, $handler); } /** * Adds a new PATCH route. * * @param string $path Request path. * @param callable $handler Request handler. */ public function patch(string $path, callable $handler): void { $this->router->add('patch', $path, $handler); } /** * Adds a new PUT route. * * @param string $path Request path. * @param callable $handler Request handler. */ public function put(string $path, callable $handler): void { $this->router->add('put', $path, $handler); } /** * Adds a new OPTIONS route. * * @param string $path Request path. * @param callable $handler Request handler. */ public function options(string $path, callable $handler): void { $this->router->add('options', $path, $handler); } }