gacela-project /
router
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types=1); |
||
| 4 | |||
| 5 | namespace Gacela\Router; |
||
| 6 | |||
| 7 | use Closure; |
||
| 8 | use Exception; |
||
| 9 | use Gacela\Container\Container; |
||
| 10 | use Gacela\Router\Configure\Bindings; |
||
| 11 | use Gacela\Router\Configure\Handlers; |
||
| 12 | use Gacela\Router\Configure\Middlewares; |
||
| 13 | use Gacela\Router\Configure\Routes; |
||
| 14 | use Gacela\Router\Entities\Request; |
||
| 15 | use Gacela\Router\Entities\Route; |
||
| 16 | use Gacela\Router\Exceptions\NonCallableHandlerException; |
||
| 17 | use Gacela\Router\Exceptions\NotFound404Exception; |
||
| 18 | use Gacela\Router\Exceptions\UnsupportedRouterConfigureCallableParamException; |
||
| 19 | use Gacela\Router\Middleware\MiddlewarePipeline; |
||
| 20 | use Override; |
||
| 21 | use ReflectionFunction; |
||
| 22 | |||
| 23 | use function get_class; |
||
| 24 | use function is_callable; |
||
| 25 | use function is_null; |
||
| 26 | |||
| 27 | final class Router implements RouterInterface |
||
| 28 | { |
||
| 29 | 112 | private Routes $routes; |
|
| 30 | private Bindings $bindings; |
||
| 31 | 112 | private Handlers $handlers; |
|
| 32 | 112 | private Middlewares $middlewares; |
|
| 33 | 112 | ||
| 34 | public function __construct(?Closure $fn = null) |
||
| 35 | 112 | { |
|
| 36 | 108 | $this->routes = new Routes(); |
|
| 37 | $this->bindings = new Bindings(); |
||
| 38 | $this->handlers = new Handlers(); |
||
| 39 | $this->middlewares = new Middlewares(); |
||
| 40 | 112 | ||
| 41 | if (!is_null($fn)) { |
||
| 42 | 112 | $this->configure($fn); |
|
| 43 | 112 | } |
|
| 44 | 112 | } |
|
| 45 | 112 | ||
| 46 | 112 | #[Override] |
|
| 47 | 112 | public function configure(Closure $fn): self |
|
| 48 | { |
||
| 49 | 111 | $params = array_map(fn ($param) => match ((string) $param->getType()) { |
|
| 50 | Routes::class => $this->routes, |
||
| 51 | 104 | Bindings::class => $this->bindings, |
|
| 52 | Handlers::class => $this->handlers, |
||
| 53 | Middlewares::class => $this->middlewares, |
||
| 54 | 104 | default => throw UnsupportedRouterConfigureCallableParamException::fromName($param), |
|
| 55 | }, (new ReflectionFunction($fn))->getParameters()); |
||
| 56 | |||
| 57 | 104 | $fn(...$params); |
|
| 58 | 18 | ||
| 59 | 18 | return $this; |
|
| 60 | } |
||
| 61 | |||
| 62 | #[Override] |
||
| 63 | 104 | public function run(): void |
|
| 64 | { |
||
| 65 | 104 | try { |
|
| 66 | 102 | $route = $this->findRoute(); |
|
| 67 | 97 | $request = Request::fromGlobals(); |
|
| 68 | |||
| 69 | $resolvedGlobalMiddlewares = $this->resolveMiddlewares($this->middlewares->getAll()); |
||
| 70 | $resolvedRouteMiddlewares = $this->resolveMiddlewares($route->getMiddlewares()); |
||
| 71 | 7 | ||
| 72 | $allMiddlewares = array_merge($resolvedGlobalMiddlewares, $resolvedRouteMiddlewares); |
||
| 73 | |||
| 74 | 18 | $pipeline = new MiddlewarePipeline($allMiddlewares); |
|
| 75 | |||
| 76 | 18 | echo $pipeline->handle($request, function () use ($route): string { |
|
| 77 | return (string) $route->run($this->bindings); |
||
| 78 | 18 | }); |
|
| 79 | 10 | } catch (Exception $exception) { |
|
| 80 | echo $this->handleException($exception); |
||
| 81 | } |
||
| 82 | } |
||
| 83 | 8 | ||
| 84 | private function findRoute(): Route |
||
| 85 | 8 | { |
|
| 86 | 7 | foreach ($this->routes->getAllRoutes() as $route) { |
|
| 87 | if ($route->requestMatches()) { |
||
| 88 | return $route; |
||
| 89 | 1 | } |
|
| 90 | } |
||
| 91 | |||
| 92 | throw new NotFound404Exception(); |
||
| 93 | } |
||
| 94 | |||
| 95 | 18 | private function handleException(Exception $exception): string |
|
| 96 | { |
||
| 97 | 18 | $handler = $this->findHandler($exception); |
|
| 98 | 18 | ||
| 99 | if (is_callable($handler)) { |
||
| 100 | /** @var string $result */ |
||
| 101 | $result = $handler($exception); |
||
| 102 | return $result; |
||
| 103 | } |
||
| 104 | |||
| 105 | /** @psalm-suppress MixedAssignment */ |
||
| 106 | $instance = Container::create($handler); |
||
| 107 | |||
| 108 | if (is_callable($instance)) { |
||
| 109 | /** @var string $result */ |
||
| 110 | $result = $instance($exception); |
||
| 111 | return $result; |
||
| 112 | } |
||
| 113 | |||
| 114 | throw NonCallableHandlerException::fromException($exception::class); |
||
| 115 | } |
||
| 116 | |||
| 117 | /** |
||
| 118 | * @return callable|class-string |
||
|
0 ignored issues
–
show
Documentation
Bug
introduced
by
Loading history...
|
|||
| 119 | */ |
||
| 120 | private function findHandler(Exception $exception): string|callable |
||
| 121 | { |
||
| 122 | return $this->handlers->getAllHandlers()[get_class($exception)] |
||
| 123 | ?? $this->handlers->getAllHandlers()[Exception::class]; |
||
| 124 | } |
||
| 125 | |||
| 126 | /** |
||
| 127 | * @param list<Middleware\MiddlewareInterface|class-string<Middleware\MiddlewareInterface>|string> $middlewares |
||
| 128 | * |
||
| 129 | * @return list<Middleware\MiddlewareInterface|class-string<Middleware\MiddlewareInterface>> |
||
| 130 | */ |
||
| 131 | private function resolveMiddlewares(array $middlewares): array |
||
| 132 | { |
||
| 133 | $resolved = []; |
||
| 134 | |||
| 135 | foreach ($middlewares as $middleware) { |
||
| 136 | array_push($resolved, ...$this->middlewares->resolve($middleware)); |
||
| 137 | } |
||
| 138 | |||
| 139 | return $resolved; |
||
| 140 | } |
||
| 141 | } |
||
| 142 |