routes[] = new Route($method, $path, $handler); return $route; } public function errorHandler(int $code, callable $handler) { $this->errorHandlers[$code] = $handler; } public function dispatch() { $paths = $this->paths(); $requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET'; $requestPath = $_SERVER['REQUEST_URI'] ?? '/'; $matching = $this->match($requestMethod, $requestPath); if ($matching) { $this->current = $matching; try { return $matching->dispatch(); } catch (Throwable $e) { $result = null; if ($handler = config('handlers.exceptions')) { $instance = new $handler(); if ($result = $instance->showThrowable($e)) { return $result; } } return $this->dispatchError(); } } if (in_array($requestPath, $paths)) { return $this->dispatchNotAllowed(); } return $this->dispatchNotFound(); } private function paths(): array { $paths = []; foreach ($this->routes as $route) { $paths[] = $route->path(); } return $paths; } public function current(): ?Route { return $this->current; } private function match(string $method, string $path): ?Route { foreach ($this->routes as $route) { if ($route->matches($method, $path)) { return $route; } } return null; } public function dispatchNotAllowed() { $this->errorHandlers[400] ??= fn() => 'not allowed'; return $this->respondWithStatus(400, $this->errorHandlers[400]()); } public function dispatchNotFound() { $this->errorHandlers[404] ??= fn() => 'not found'; return $this->respondWithStatus(404, $this->errorHandlers[404]()); } public function dispatchError() { $this->errorHandlers[500] ??= fn() => 'server error'; return $this->respondWithStatus(500, $this->errorHandlers[500]()); } /** * Error handlers default to returning a plain string, so if that's what * we got, wrap it in a Response with the correct status code. A handler * that already returned its own Response is left alone. */ private function respondWithStatus(int $status, mixed $result): Response { if ($result instanceof Response) { return $result; } return response()->status($status)->content($result); } public function route(string $name, array $parameters = []): string { foreach ($this->routes as $route) { if ($route->name() === $name) { $finds = []; $replaces = []; foreach ($parameters as $key => $value) { array_push($finds, "{{$key}}"); array_push($replaces, $value); array_push($finds, "{{$key}?}"); array_push($replaces, $value); } $path = $route->path(); $path = str_replace($finds, $replaces, $path); $path = preg_replace('#{[^}]+}#', '', $path); return $path; } } throw new Exception('No route with that name'); } }