Passed
Push — main ( b99b54...954687 )
by Chema
01:08 queued 12s
created

Route::resetCache()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
ccs 2
cts 2
cp 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Router;
6
7
use ReflectionClass;
8
use ReflectionNamedType;
9
10
use function count;
11
use function is_object;
12
13
/**
14
 * @method static get(string $path, object|string $controller, string $action = '__invoke')
15
 * @method static head(string $path, object|string $controller, string $action = '__invoke')
16
 * @method static connect(string $path, object|string $controller, string $action = '__invoke')
17
 * @method static post(string $path, object|string $controller, string $action = '__invoke')
18
 * @method static delete(string $path, object|string $controller, string $action = '__invoke')
19
 * @method static options(string $path, object|string $controller, string $action = '__invoke')
20
 * @method static patch(string $path, object|string $controller, string $action = '__invoke')
21
 * @method static put(string $path, object|string $controller, string $action = '__invoke')
22
 * @method static trace(string $path, object|string $controller, string $action = '__invoke')
23
 */
24
final class Route
25
{
26
    private static ?Request $request = null;
27
28
    /**
29
     * @param object|class-string $controller
0 ignored issues
show
Documentation Bug introduced by
The doc comment object|class-string at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in object|class-string.
Loading history...
30
     */
31 42
    public function __construct(
32
        private string $method,
33
        private string $path,
34
        private object|string $controller,
35
        private string $action = '__invoke',
36
    ) {
37 42
    }
38
39 42
    public static function resetCache(): void
40
    {
41 42
        self::$request = null;
42
    }
43
44
    /**
45
     * @param callable(RoutingConfigurator):void $fn
46
     */
47 42
    public static function configure(callable $fn): void
48
    {
49 42
        $routingConfigurator = new RoutingConfigurator();
50 42
        $fn($routingConfigurator);
51
52 42
        foreach ($routingConfigurator->routes() as $route) {
53 42
            if ($route->requestMatches()) {
54 40
                echo $route->run();
55 40
                break;
56
            }
57
        }
58
    }
59
60
    /**
61
     * @psalm-suppress MixedMethodCall
62
     */
63 40
    public function run(): string
64
    {
65 40
        if (is_object($this->controller)) {
66
            return (string)$this->controller
67
                ->{$this->action}(
68
                    ...$this->getParams()
69
                );
70
        }
71 40
        return (string)(new $this->controller())
72 40
            ->{$this->action}(
73 40
                ...$this->getParams()
74 40
            );
75
    }
76
77 42
    private function requestMatches(): bool
78
    {
79 42
        if (!$this->methodMatches()) {
80 1
            return false;
81
        }
82
83 41
        if (!$this->pathMatches()) {
84 1
            return false;
85
        }
86
87 40
        return true;
88
    }
89
90 42
    private function methodMatches(): bool
91
    {
92 42
        return $this->request()->isMethod($this->method);
93
    }
94
95 41
    private function pathMatches(): bool
96
    {
97 41
        return (bool)preg_match($this->getPathPattern(), $this->request()->path())
98 41
            || (bool)preg_match($this->getPathPatternWithoutOptionals(), $this->request()->path());
99
    }
100
101 41
    private function getPathPattern(): string
102
    {
103 41
        $pattern = preg_replace('#({.*})#U', '(.*)', $this->path);
104
105 41
        return '#^/' . $pattern . '$#';
106
    }
107
108 3
    private function getPathPatternWithoutOptionals(): string
109
    {
110 3
        $pattern = preg_replace('#/({.*\?})#U', '(/(.*))?', $this->path);
111
112 3
        return '#^/' . $pattern . '$#';
113
    }
114
115 40
    private function getParams(): array
116
    {
117 40
        $params = [];
118 40
        $pathParamKeys = [];
119 40
        $pathParamValues = [];
120
121 40
        preg_match($this->getPathPattern(), '/' . $this->path, $pathParamKeys);
122 40
        preg_match($this->getPathPattern(), $this->request()->path(), $pathParamValues);
123
124 40
        unset($pathParamValues[0], $pathParamKeys[0]);
125 40
        $pathParamKeys = array_map(static fn ($key) => trim($key, '{}'), $pathParamKeys);
126
127 40
        while (count($pathParamValues) !== count($pathParamKeys)) {
128 2
            array_shift($pathParamKeys);
129
        }
130
131 40
        $pathParams = array_combine($pathParamKeys, $pathParamValues);
132 40
        $actionParams = (new ReflectionClass($this->controller))
133 40
            ->getMethod($this->action)
134 40
            ->getParameters();
135
136 40
        foreach ($actionParams as $actionParam) {
137 36
            $paramName = $actionParam->getName();
138
            /** @var string|null $paramType */
139 36
            $paramType = null;
140
141 36
            if ($actionParam->getType() && is_a($actionParam->getType(), ReflectionNamedType::class)) {
142 36
                $paramType = $actionParam->getType()->getName();
143
            }
144
145 36
            $value = match ($paramType) {
146 36
                'string' => $pathParams[$paramName] ?? '',
147 36
                'int' => (int)($pathParams[$paramName] ?? 0),
148 36
                'float' => (float)($pathParams[$paramName] ?? 0.0),
149 36
                'bool' => (bool)json_decode($pathParams[$paramName] ?? '0'),
150 36
                null => null,
151 36
            };
152
153 36
            $params[$paramName] = $value;
154
        }
155
156 40
        return $params;
157
    }
158
159 42
    private function request(): Request
160
    {
161 42
        return self::$request ??= Request::fromGlobals();
162
    }
163
}
164