Passed
Push — main ( 565e18...f372ec )
by Chema
55s queued 13s
created

Router   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 108
Duplicated Lines 0 %

Test Coverage

Coverage 82%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
eloc 42
dl 0
loc 108
ccs 41
cts 50
cp 0.82
rs 10
c 6
b 0
f 0
wmc 15

7 Methods

Rating   Name   Duplication   Size   Complexity  
A get() 0 6 1
A withServer() 0 5 1
A __construct() 0 4 1
A parameters() 0 13 4
A requestUrl() 0 5 2
A listen() 0 15 2
A route() 0 28 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PhpLightning\Router;
6
7
use function count;
8
use function is_callable;
9
10
final class Router
11
{
12
    /**
13
     * Eg: [method -> url -> [action, args]]
14
     *
15
     * @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...
16
     *     controller: string|callable,
17
     *     action: string,
18
     *     args?: array
19
     * }>>
20
     */
21
    private array $routes = [];
22
23 3
    private function __construct(
24
        private string $requestMethod,
25
        private string $requestUri,
26
    ) {
27 3
    }
28
29 3
    public static function withServer(array $server = []): self
30
    {
31 3
        return new self(
32 3
            (string)($server['REQUEST_METHOD'] ?? ''),
33 3
            (string)($server['REQUEST_URI'] ?? ''),
34 3
        );
35
    }
36
37 3
    public function listen(): void
38
    {
39 3
        $requestUrl = $this->requestUrl();
40
41 3
        $notFoundFn = static fn (): string => "404: route '{$requestUrl}' not found";
42 3
        $current = $this->routes[$this->requestMethod][$requestUrl]
43 1
            ?? ['controller' => $notFoundFn, 'action' => '', 'args' => []];
44
45 3
        if (is_callable($current['controller'])) {
46
            /** @psalm-suppress TooManyArguments */
47 3
            echo (string)$current['controller'](...$current['args'] ?? []);
48
        } else {
49
            /** @psalm-suppress TooManyArgument,InvalidStringClass,PossiblyUndefinedArrayOffset */
50
            echo (string)(new $current['controller']())
51
                ->{$current['action']}(...$current['args'] ?? []);
52
        }
53
    }
54
55 2
    public function get(
56
        string $route,
57
        callable|string $controller,
58
        string $action = '__invoke',
59
    ): void {
60 2
        $this->route('GET', $route, $controller, $action);
61
    }
62
63 2
    private function route(
64
        string $method,
65
        string $route,
66
        callable|string $controller,
67
        string $action = '',
68
    ): void {
69 2
        $requestUrl = $this->requestUrl();
70 2
        $requestUrlParts = explode('/', $requestUrl);
71 2
        $routeParts = explode('/', $route);
72
73 2
        if (count($routeParts) !== count($requestUrlParts)) {
74
            return;
75
        }
76
77 2
        array_shift($routeParts);
78 2
        array_shift($requestUrlParts);
79
80 2
        if ($routeParts[0] === '' && count($requestUrlParts) === 0) {
81
            $this->routes[$method][$requestUrl] = [
82
                'controller' => $controller,
83
                'action' => $action,
84
                'args' => [],
85
            ];
86
        } else {
87 2
            $this->routes[$method][$requestUrl] = [
88 2
                'controller' => $controller,
89 2
                'action' => $action,
90 2
                'args' => $this->parameters($routeParts, $requestUrlParts),
91 2
            ];
92
        }
93
    }
94
95 3
    private function requestUrl(): string
96
    {
97 3
        $requestUrl = (string)filter_var($this->requestUri, FILTER_SANITIZE_URL);
98 3
        $requestUrl = (string)strtok($requestUrl, '?');
99 3
        return rtrim($requestUrl, '/') ?: '/';
100
    }
101
102
    /**
103
     * @psalm-suppress MixedAssignment,MixedArgument
104
     */
105 2
    private function parameters(array $routeParts, array $requestUrlParts): array
106
    {
107 2
        $parameters = [];
108 2
        for ($i = 0, $iMax = count($routeParts); $i < $iMax; ++$i) {
109 2
            $routePart = $routeParts[$i];
110 2
            if (preg_match('/^[$]/', $routePart)) {
111 1
                $parameters[] = $requestUrlParts[$i];
112 1
            } elseif ($routeParts[$i] !== $requestUrlParts[$i]) {
113
                return [];
114
            }
115
        }
116
117 2
        return $parameters;
118
    }
119
}
120