Steuerkram/src/Router.php

89 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
namespace Steuerkram;
use Steuerkram\Auth;
class Router
{
private array $routes = [];
public function add(string $method, string $pattern, callable $handler): void
{
$this->routes[] = [
'method' => strtoupper($method),
'pattern' => $pattern,
'handler' => $handler,
];
}
public function get(string $pattern, callable $handler): void
{
$this->add('GET', $pattern, $handler);
}
public function post(string $pattern, callable $handler): void
{
$this->add('POST', $pattern, $handler);
}
public function put(string $pattern, callable $handler): void
{
$this->add('PUT', $pattern, $handler);
}
public function delete(string $pattern, callable $handler): void
{
$this->add('DELETE', $pattern, $handler);
}
public function dispatch(): void
{
$method = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
// Support method override via hidden _method field or X-HTTP-Method-Override header
if ($method === 'POST') {
$override = $_POST['_method'] ?? $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? null;
if ($override) {
$method = strtoupper($override);
}
}
$uri = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
$uri = '/' . trim($uri, '/');
if ($uri === '') $uri = '/';
if (in_array($method, ['POST', 'PUT', 'DELETE', 'PATCH'], true)) {
$token = $_POST['csrf_token'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '';
Auth::validateCsrfToken($token);
}
foreach ($this->routes as $route) {
if ($route['method'] !== $method) continue;
$params = $this->match($route['pattern'], $uri);
if ($params !== null) {
($route['handler'])($params);
return;
}
}
http_response_code(404);
$view = new View();
$view->render('layout/404.php', ['uri' => $uri]);
}
private function match(string $pattern, string $uri): ?array
{
// Convert route pattern to regex: {id} → named capture
$regex = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $pattern);
$regex = '#^' . $regex . '$#';
if (preg_match($regex, $uri, $matches)) {
return array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
}
return null;
}
}