You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

147 line
3.8KB

  1. <?php
  2. namespace Framework\Routing;
  3. use Framework\Http\Response;
  4. use Framework\Routing\Exception;
  5. use Throwable;
  6. class Router
  7. {
  8. protected array $routes = [];
  9. protected array $errorHandlers = [];
  10. protected Route $current;
  11. public function add(string $method, string $path, $handler): Route
  12. {
  13. $route = $this->routes[] = new Route($method, $path, $handler);
  14. return $route;
  15. }
  16. public function errorHandler(int $code, callable $handler)
  17. {
  18. $this->errorHandlers[$code] = $handler;
  19. }
  20. public function dispatch()
  21. {
  22. $paths = $this->paths();
  23. $requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET';
  24. $requestPath = $_SERVER['REQUEST_URI'] ?? '/';
  25. $matching = $this->match($requestMethod, $requestPath);
  26. if ($matching) {
  27. $this->current = $matching;
  28. try {
  29. return $matching->dispatch();
  30. }
  31. catch (Throwable $e) {
  32. $result = null;
  33. if ($handler = config('handlers.exceptions')) {
  34. $instance = new $handler();
  35. if ($result = $instance->showThrowable($e)) {
  36. return $result;
  37. }
  38. }
  39. return $this->dispatchError();
  40. }
  41. }
  42. if (in_array($requestPath, $paths)) {
  43. return $this->dispatchNotAllowed();
  44. }
  45. return $this->dispatchNotFound();
  46. }
  47. private function paths(): array
  48. {
  49. $paths = [];
  50. foreach ($this->routes as $route) {
  51. $paths[] = $route->path();
  52. }
  53. return $paths;
  54. }
  55. public function current(): ?Route
  56. {
  57. return $this->current;
  58. }
  59. private function match(string $method, string $path): ?Route
  60. {
  61. foreach ($this->routes as $route) {
  62. if ($route->matches($method, $path)) {
  63. return $route;
  64. }
  65. }
  66. return null;
  67. }
  68. public function dispatchNotAllowed()
  69. {
  70. $this->errorHandlers[400] ??= fn() => 'not allowed';
  71. return $this->respondWithStatus(400, $this->errorHandlers[400]());
  72. }
  73. public function dispatchNotFound()
  74. {
  75. $this->errorHandlers[404] ??= fn() => 'not found';
  76. return $this->respondWithStatus(404, $this->errorHandlers[404]());
  77. }
  78. public function dispatchError()
  79. {
  80. $this->errorHandlers[500] ??= fn() => 'server error';
  81. return $this->respondWithStatus(500, $this->errorHandlers[500]());
  82. }
  83. /**
  84. * Error handlers default to returning a plain string, so if that's what
  85. * we got, wrap it in a Response with the correct status code. A handler
  86. * that already returned its own Response is left alone.
  87. */
  88. private function respondWithStatus(int $status, mixed $result): Response
  89. {
  90. if ($result instanceof Response) {
  91. return $result;
  92. }
  93. return response()->status($status)->content($result);
  94. }
  95. public function route(string $name, array $parameters = []): string
  96. {
  97. foreach ($this->routes as $route) {
  98. if ($route->name() === $name) {
  99. $finds = [];
  100. $replaces = [];
  101. foreach ($parameters as $key => $value) {
  102. array_push($finds, "{{$key}}");
  103. array_push($replaces, $value);
  104. array_push($finds, "{{$key}?}");
  105. array_push($replaces, $value);
  106. }
  107. $path = $route->path();
  108. $path = str_replace($finds, $replaces, $path);
  109. $path = preg_replace('#{[^}]+}#', '', $path);
  110. return $path;
  111. }
  112. }
  113. throw new Exception('No route with that name');
  114. }
  115. }

Powered by TurnKey Linux.