Passed
Push — main ( e120fa...565e18 )
by Chema
53s queued 13s
created

Router::route()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4.5923

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 4
eloc 16
c 3
b 0
f 0
nc 3
nop 3
dl 0
loc 24
ccs 12
cts 18
cp 0.6667
crap 4.5923
rs 9.7333
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpLightning\Router;
6
7
use function count;
8
9
final class Router
10
{
11
    /**
12
     * Eg: [method -> url -> [action, args]]
13
     *
14
     * @var array<string, array<string, array{
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string, array<string, array{ at position 10 could not be parsed: the token is null at position 10.
Loading history...
15
     *     action: callable,
16
     *     args?: array
17
     * }>>
18
     */
19
    private array $routes = [];
20
21 3
    private function __construct(
22
        private string $requestMethod,
23
        private string $requestUri,
24
    ) {
25 3
    }
26
27 3
    public static function withServer(array $server = []): self
28
    {
29 3
        return new self(
30 3
            (string)($server['REQUEST_METHOD'] ?? ''),
31 3
            (string)($server['REQUEST_URI'] ?? ''),
32 3
        );
33
    }
34
35 3
    public function listen(): void
36
    {
37 3
        $requestUrl = $this->requestUrl();
38
39 3
        $notFoundFn = static fn (): string => "404: route '{$requestUrl}' not found";
40
41 3
        $current = $this->routes[$this->requestMethod][$requestUrl]
42 1
            ?? ['action' => $notFoundFn, 'args' => []];
43
44
        /** @psalm-suppress TooManyArguments */
45 3
        echo (string)$current['action'](...$current['args'] ?? []);
46
    }
47
48 2
    public function get(string $route, callable $callback): void
49
    {
50 2
        $this->route('GET', $route, $callback);
51
    }
52
53 2
    private function route(string $method, string $route, callable $callback): void
54
    {
55 2
        $requestUrl = $this->requestUrl();
56 2
        $routeParts = explode('/', $route);
57 2
        $requestUrlParts = explode('/', $requestUrl);
58 2
        array_shift($routeParts);
59 2
        array_shift($requestUrlParts);
60
61 2
        if ($routeParts[0] === '' && count($requestUrlParts) === 0) {
62
            $this->routes[$method][$requestUrl] = [
63
                'action' => $callback,
64
                'args' => [],
65
            ];
66
            return;
67
        }
68
69 2
        if (count($routeParts) !== count($requestUrlParts)) {
70
            return;
71
        }
72
73 2
        $parameters = $this->parameters($routeParts, $requestUrlParts);
74 2
        $this->routes[$method][$requestUrl] = [
75 2
            'action' => $callback,
76 2
            'args' => $parameters,
77 2
        ];
78
    }
79
80 3
    private function requestUrl(): string
81
    {
82 3
        $requestUrl = (string)filter_var($this->requestUri, FILTER_SANITIZE_URL);
83 3
        $requestUrl = (string)strtok($requestUrl, '?');
84
85 3
        return rtrim($requestUrl, '/');
86
    }
87
88
    /**
89
     * @psalm-suppress MixedAssignment,MixedArgument
90
     */
91 2
    private function parameters(array $routeParts, array $requestUrlParts): array
92
    {
93 2
        $parameters = [];
94 2
        for ($i = 0, $iMax = count($routeParts); $i < $iMax; ++$i) {
95 2
            $routePart = $routeParts[$i];
96 2
            if (preg_match('/^[$]/', $routePart)) {
97 1
                $parameters[] = $requestUrlParts[$i];
98 1
            } elseif ($routeParts[$i] !== $requestUrlParts[$i]) {
99
                return [];
100
            }
101
        }
102
103 2
        return $parameters;
104
    }
105
}
106