Router::resolveMiddlewares()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 9
rs 10
c 0
b 0
f 0
eloc 4
nc 2
nop 1
ccs 0
cts 0
cp 0
crap 6
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
The doc comment callable|class-string at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in callable|class-string.
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