Lines 67.28% 72 / 107
Methods 36.84% 7 / 19
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 registerRoute 100.00% 7 / 7 100.00% 1 / 1 1
 loadRoutes 0.00% 0 / 4 0.00% 0 / 1 6
 [Lucent\Router] setDefaultController 0.00% 0 / 1 0.00% 0 / 1 2
 [Lucent\Router] resolveController 0.00% 0 / 1 0.00% 0 / 1 2
 [Lucent\Router] group 0.00% 0 / 17 0.00% 0 / 1 6
 [Lucent\Router] parseRouteParameter 100.00% 3 / 3 100.00% 1 / 1 2
 [Lucent\Router] disable 100.00% 4 / 4 100.00% 1 / 1 3
 [Lucent\Router] isDisabled 100.00% 1 / 1 100.00% 1 / 1 2
 [Lucent\Router] getUriAsArray 68.75% 11 / 16 0.00% 0 / 1 7.10
 [Lucent\Router] analyseRouteAndLookup 100.00% 8 / 8 100.00% 1 / 1 5
 [Lucent\Router] matchRoute 100.00% 24 / 24 100.00% 1 / 1 6
 [Lucent\Router] setActiveMiddleware 0.00% 0 / 1 0.00% 0 / 1 2
 [Lucent\Router] setPrefix 0.00% 0 / 1 0.00% 0 / 1 2
 [Lucent\Router] getPrefix 0.00% 0 / 1 0.00% 0 / 1 2
 [Lucent\Router] getMiddleware 0.00% 0 / 1 0.00% 0 / 1 2
 [Lucent\Router] getFullPath 0.00% 0 / 1 0.00% 0 / 1 6
 [Lucent\Router] getFullClassName 0.00% 0 / 1 0.00% 0 / 1 6
 [Lucent\Router] reset 100.00% 7 / 7 100.00% 1 / 1 1
 [Lucent\Router] getRoutes 87.50% 7 / 8 0.00% 0 / 1 5.05
12class CliRouter extends Router
13{
14    /**
15     * Register a new route with optional controller and middleware
16     */
17    public function registerRoute(string $uri, string $type, string $method, ?string $controller = null, array $middleware = [], ?string $description = null): void
18    {
19        $uri = str_replace(":", " ", $uri);
20        $this->routes[$type][$uri] = [
21            "controller" => $controller,
22            "method" => $method,
23            "middleware" => array_merge($this->middleware, $middleware),
24            "description" => $description
25        ];
26    }
27
28    /**
29     * Load routes from a file
30     * @throws FileNotFound
31     */
32    public function loadRoutes(string $file, ?string $prefix = null): void
33    {
34        // Detect absolute paths (e.g. from glob() in Application::boot()) so
35        // the File constructor doesn't prepend rootPath() a second time.
36        $routes = new File($file, null, FileSystem::isAbsolute($file));
37
38        if (!$routes->exists()) {
39            throw new FileNotFound($routes->path);
40        }
41
42        require_once $routes->path;
43    }
44}

Inherited from Lucent\Router

44    public function setDefaultController(?string $controller): void
45    {
46        $this->defaultController = $controller;
47    }
52    protected function resolveController(?string $routeController): ?string
53    {
54        return $routeController ?? $this->defaultController;
55    }
60    public function group(array $attributes, callable $callback): void
61    {
62        $this->groupStack[] = [
63            'middleware' => $this->middleware,
64            'prefix' => $this->prefix,
65            'namespace' => $this->namespace,
66            'defaultController' => $this->defaultController
67        ];
68
69        // Merge new group attributes
70        $this->middleware = array_merge($this->middleware, $attributes['middleware'] ?? []);
71        // Append the new prefix onto the existing one, preserving the
72        // accumulation behaviour for nested groups (e.g. /api + /v1 = /api/v1).
73        // When no prefix is provided, keep the current value (which may be
74        // null) rather than converting it to an empty string.
75        if (array_key_exists('prefix', $attributes)) {
76            $this->prefix = ($this->prefix ?? '') . $attributes['prefix'];
77        }
78        // else: $this->prefix unchanged (preserves null distinct from '')
79        $this->namespace = $attributes['namespace'] ?? $this->namespace;
80        $this->defaultController = $attributes['defaultController'] ?? $this->defaultController;
81
82        // Execute the group's route definitions
83        $callback($this);
84
85        // Restore previous group attributes
86        $previous = array_pop($this->groupStack);
87        $this->middleware = $previous['middleware'];
88        $this->prefix = $previous['prefix'];
89        $this->namespace = $previous['namespace'];
90        $this->defaultController = $previous['defaultController'];
91    }
97    protected function parseRouteParameter(string $segment): array
98    {
99        if (preg_match('/^\{([a-zA-Z_][a-zA-Z0-9_]*)\}$/', $segment, $matches)) {
100            return [true, $matches[1]];
101        }
102        return [false, ''];
103    }
109    public function disable(string|array $route): void
110    {
111        if (is_array($route)) {
112            foreach ($route as $r) {
113                $this->disabled[$r] = true;
114            }
115        } else {
116            $this->disabled[$route] = true;
117        }
118    }
123    protected function isDisabled(string $uri): bool
124    {
125        return ($this->disabled["*"] ?? false) || ($this->disabled[$uri] ?? false);
126    }
134    public function getUriAsArray(?string $url = null, string $separator = "/"): array
135    {
136        // If no URL provided, check for CLI arguments
137        if ($url === null) {
138            // Check if running in CLI mode
139            if (php_sapi_name() === 'cli') {
140                // Use global $_SERVER['argv'] for CLI arguments
141                $url = implode(' ', array_slice($_SERVER['argv'], 1));
142            } else {
143                $url = $_SERVER["REQUEST_URI"] ?? '';
144            }
145        }
146
147        // Remove any protocol, domain, or query string
148        $url = parse_url($url, PHP_URL_PATH);
149        if ($url === false || $url === null) {
150            $url = '';
151        }
152
153        // Decode URL to handle special characters
154        $url = urldecode($url);
155
156        // Remove query string if present (must use !== false - strpos returns 0
157        // when '?' is the first character, which is falsy)
158        $pos = strpos($url, "?");
159        if ($pos !== false) {
160            $url = substr($url, 0, $pos);
161        }
162
163        // Normalize URL for CLI
164        $url = preg_replace('/\s+/', $separator, trim($url));
165
166        // Normalize slashes and trim
167        $url = trim($url, '/');
168
169        // Split and filter empty segments
170        return array_values(array_filter(explode($separator, $url), function($segment) {
171            return $segment !== '';
172        }));
173    }
177    public function analyseRouteAndLookup(array $route, string $method): array
178    {
179        if (!isset($this->routes[$method])) {
180            throw new HttpException(HttpStatus::NOT_FOUND);
181        }
182
183        foreach ($this->routes[$method] as $key => $routeDefinition) {
184            if ($match = $this->matchRoute($key, $route, '/', $routeDefinition, $method)) {
185                if ($this->isDisabled($key)) {
186                    throw new HttpException(HttpStatus::FORBIDDEN);
187                }
188                return $match;
189            }
190        }
191
192
193        throw new HttpException(HttpStatus::NOT_FOUND);
194    }
199    protected function matchRoute(string $routePath, array $uri, string $separator, array $route, string $method): ?array
200    {
201        // Normalize route path
202        $routePath = trim($routePath, '/');
203        $routeSegments = $this->getUriAsArray($routePath, $separator);
204
205        if (count($routeSegments) !== count($uri)) {
206            return null;
207        }
208
209        $variables = [];
210        $matches = true;
211
212        for ($i = 0; $i < count($routeSegments); $i++) {
213            [$isParameter, $paramName] = $this->parseRouteParameter($routeSegments[$i]);
214
215            if (!$isParameter) {
216                if ($routeSegments[$i] !== $uri[$i]) {
217                    $matches = false;
218                    break;
219                }
220            } else {
221                $variables[$paramName] = $uri[$i];
222            }
223        }
224
225        if (!$matches) {
226            return null;
227        }
228
229        Log::channel('lucent.routing')->info("[Router] Found route : " . $routePath."\n    Http Method:".$method."\n    Http Controller: {$route["controller"]}@{$route["method"]}");
230
231        return [
232            "route" => $routePath,
233            "outcome" => true,
234            "controller" => $route["controller"],
235            "method" => $route["method"],
236            "variables" => $variables,
237            "middleware" => $route["middleware"] ?? []
238        ];
239    }
244    public function setActiveMiddleware(array $middleware): void
245    {
246        $this->middleware = $middleware;
247    }
252    public function setPrefix(?string $prefix): void
253    {
254        $this->prefix = $prefix;
255    }
260    public function getPrefix(): ?string
261    {
262        return $this->prefix;
263    }
268    public function getMiddleware(): array
269    {
270        return $this->middleware;
271    }
276    protected function getFullPath(string $path): string
277    {
278        return $this->prefix ? rtrim($this->prefix, '/') . '/' . ltrim($path, '/') : $path;
279    }
284    protected function getFullClassName(string $controller): string
285    {
286        return $this->namespace ? rtrim($this->namespace, '\\') . '\\' . $controller : $controller;
287    }
289    public function reset(): void
290    {
291        $this->routes = [];
292        $this->groupStack = [];
293        $this->middleware = [];
294        $this->prefix = null;
295        $this->namespace = null;
296        $this->defaultController = null;
297        $this->disabled = ["*" => false];
298    }
300    public function getRoutes(bool $activeOnly = true): array
301    {
302        if ($activeOnly) {
303            $active = [];
304            foreach ($this->routes as $method => $routes) {
305                foreach ($routes as $uri => $route) {
306                    if (!$this->isDisabled($uri)) {
307                        $active[$method][$uri] = $route;
308                    }
309                }
310            }
311            return $active;
312        }
313
314        return $this->routes;
315    }