Files
scoutiq/app/Core/Router.php

114 lines
3.5 KiB
PHP

<?php
namespace App\Core;
use Exception;
class Router
{
private array $routes = [];
private array $groupMiddleware = [];
private string $groupPrefix = '';
/**
* Map a route.
*/
public function addRoute(string $method, string $path, array|callable $callback, array $middleware = []): void
{
$prefix = '/' . trim($this->groupPrefix, '/');
$prefix = $prefix === '/' ? '' : $prefix;
$trimmedPath = '/' . trim($path, '/');
$trimmedPath = $trimmedPath === '/' ? '' : $trimmedPath;
$fullPath = $prefix . $trimmedPath;
$fullPath = $fullPath === '' ? '/' : $fullPath;
$this->routes[strtoupper($method)][] = [
'path' => $fullPath,
'callback' => $callback,
'middleware' => array_merge($this->groupMiddleware, $middleware)
];
}
public function get(string $path, array|callable $callback, array $middleware = []): void
{
$this->addRoute('GET', $path, $callback, $middleware);
}
public function post(string $path, array|callable $callback, array $middleware = []): void
{
$this->addRoute('POST', $path, $callback, $middleware);
}
public function put(string $path, array|callable $callback, array $middleware = []): void
{
$this->addRoute('PUT', $path, $callback, $middleware);
}
public function delete(string $path, array|callable $callback, array $middleware = []): void
{
$this->addRoute('DELETE', $path, $callback, $middleware);
}
/**
* Group routes under attributes like prefix and middleware.
*/
public function group(array $attributes, callable $callback): void
{
$previousPrefix = $this->groupPrefix;
$previousMiddleware = $this->groupMiddleware;
if (isset($attributes['prefix'])) {
$this->groupPrefix = $previousPrefix . '/' . trim($attributes['prefix'], '/');
}
if (isset($attributes['middleware'])) {
$this->groupMiddleware = array_merge($previousMiddleware, (array)$attributes['middleware']);
}
$callback($this);
$this->groupPrefix = $previousPrefix;
$this->groupMiddleware = $previousMiddleware;
}
/**
* Match path against configured routes.
*/
public function resolve(Request $request): array
{
$method = $request->getMethod();
$path = '/' . trim($request->getPath(), '/');
$routes = $this->routes[$method] ?? [];
foreach ($routes as $route) {
$pattern = $this->compilePattern($route['path']);
if (preg_match($pattern, $path, $matches)) {
// Filter out non-string keys from named capture groups
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
return [
'callback' => $route['callback'],
'middleware' => $route['middleware'],
'params' => $params
];
}
}
throw new Exception("Route not found", 404);
}
/**
* Convert route notation {param} into regex capture groups.
*/
private function compilePattern(string $path): string
{
$cleanPath = '/' . trim($path, '/');
$cleanPath = $cleanPath === '/' ? '' : $cleanPath;
$pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<$1>[^/]+)', $cleanPath);
return '#^' . ($pattern === '' ? '/' : $pattern) . '$#';
}
}