Route::normalizeUrl()   A
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 13
rs 9.2222
cc 6
nc 5
nop 1
1
<?php
2
3
namespace DevPontes\Route;
4
5
use DevPontes\Route\Traits\Matcher;
6
use DevPontes\Route\Exception\ErrorRoute;
7
8
/**
9
 * Description of Route
10
 *
11
 * @author Moises Pontes
12
 * @package DevPontes\Route
13
 */
14
class Route
15
{
16
    use Matcher;
17
18
    /** @var ErrorRoute */
19
    private $fail;
20
21
    /** @var string */
22
    private $namespace;
23
24
    /** @var array */
25
    private $url;
26
27
    /** @var array */
28
    private $routes = [];
29
30
    /** @var boolean */
31
    private $strictMode = false;
32
33
    /**
34
     * Route constructor.
35
     *
36
     * @param array $routes List of registered routes
37
     * @param string $url Request URL
38
     */
39
    public function __construct(array $routes, string $url = '')
40
    {
41
        $this->setUrl($url);
42
        $this->setRoutes($routes);
43
    }
44
45
    /**
46
     * @return ErrorRoute|null
47
     */
48
    public function fail(): ?ErrorRoute
49
    {
50
        return $this->fail;
51
    }
52
53
    /**
54
     * Enable or disable strict mode for URL normalization.
55
     *
56
     * - When strict mode is enabled, trailing slashes are preserved.
57
     * - When disabled, trailing slashes are removed (except for root "/").
58
     *
59
     * @param boolean $strictMode
60
     * @return self
61
     */
62
    public function setStrictMode(bool $strictMode): self
63
    {
64
        $this->strictMode = $strictMode;
65
        return $this;
66
    }
67
68
    /**
69
     * Set the URL
70
     *
71
     * @param string $url Request URL
72
     * @return self
73
     */
74
    public function setUrl(string $url): self
75
    {
76
        $this->url = explode('/', $this->normalizeUrl($url));
77
78
        return $this;
79
    }
80
81
    /**
82
     * Set namespace app
83
     *
84
     * @param string $namespace
85
     * @return Route
86
     */
87
    public function namespace(string $namespace): Route
88
    {
89
        $this->namespace = $namespace;
90
        return $this;
91
    }
92
93
    /**
94
     *  Normalize a URL string:
95
     *
96
     * - Trim spaces and collapse multiple slashes into one
97
     * - Remove trailing slash (except root "/") when strictMode = false
98
     * - Ensure the URL always starts with "/"
99
     *
100
     * @param string $url
101
     * @return string
102
     */
103
    private function normalizeUrl(string $url): string
104
    {
105
        $url = preg_replace('#/+#', '/', trim($url));
106
107
        if ($url === '' || $url === false) {
108
            return '/';
109
        }
110
111
        if (!$this->strictMode && $url !== '/') {
112
            $url = rtrim($url, '/');
113
        }
114
115
        return $url[0] === '/' ? $url : '/' . $url;
116
    }
117
118
    /**
119
     * Config the routes
120
     *
121
     * @param array $routes
122
     * @return void
123
     */
124
    private function setRoutes(array $routes): void
125
    {
126
        foreach ($routes as $route) {
127
            if (!isset($route[0], $route[1])) {
128
                throw new ErrorRoute("Route must be defined as ['/url', 'Controller@method']");
129
            }
130
131
            if (!str_contains($route[1], '@')) {
132
                throw new ErrorRoute("Invalid route configuration: ({$route[0]}) => {$route[1]}");
133
            }
134
135
            [$controller, $method] = explode('@', $route[1]);
136
            $url = preg_replace('/\{[^}]+\}/', '{}', $route[0]);
137
            $this->routes[] = new Router($url, $method, $controller);
138
        }
139
    }
140
141
    /**
142
     * Run route
143
     *
144
     * @return void
145
     */
146
    public function run(): void
147
    {
148
        try {
149
            $router = $this->match($this->url);
150
151
            if (empty($router)) {
152
                throw new ErrorRoute("Page not found", 404);
153
            } else {
154
                $router->execute($this->namespace);
155
            }
156
        } catch (ErrorRoute $err) {
157
            $this->fail = $err;
158
        }
159
    }
160
}
161