114 lines
3.5 KiB
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) . '$#';
|
|
}
|
|
}
|