132 lines
4.4 KiB
PHP
132 lines
4.4 KiB
PHP
<?php
|
|
|
|
namespace App\Core;
|
|
|
|
/**
|
|
* Basic regex-based router supporting dynamic parameters, middlewares, and CORS OPTIONS.
|
|
*/
|
|
class Router
|
|
{
|
|
private array $routes = [];
|
|
private array $globalMiddleware = [];
|
|
|
|
/**
|
|
* Define a GET route
|
|
*/
|
|
public function get(string $path, $handler, array $middleware = []): void
|
|
{
|
|
$this->addRoute('GET', $path, $handler, $middleware);
|
|
}
|
|
|
|
/**
|
|
* Define a POST route
|
|
*/
|
|
public function post(string $path, $handler, array $middleware = []): void
|
|
{
|
|
$this->addRoute('POST', $path, $handler, $middleware);
|
|
}
|
|
|
|
/**
|
|
* Define a PUT route
|
|
*/
|
|
public function put(string $path, $handler, array $middleware = []): void
|
|
{
|
|
$this->addRoute('PUT', $path, $handler, $middleware);
|
|
}
|
|
|
|
/**
|
|
* Define a DELETE route
|
|
*/
|
|
public function delete(string $path, $handler, array $middleware = []): void
|
|
{
|
|
$this->addRoute('DELETE', $path, $handler, $middleware);
|
|
}
|
|
|
|
/**
|
|
* Add global middleware applied to all routes
|
|
*/
|
|
public function use($middleware): void
|
|
{
|
|
$this->globalMiddleware[] = $middleware;
|
|
}
|
|
|
|
private function addRoute(string $method, string $path, $handler, array $middleware): void
|
|
{
|
|
// Convert path matching expressions: /api/tickets/{id} -> regex
|
|
$pattern = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<$1>[^/]+)', $path);
|
|
$pattern = '#^' . $pattern . '$#';
|
|
|
|
$this->routes[] = [
|
|
'method' => $method,
|
|
'path' => $path,
|
|
'pattern' => $pattern,
|
|
'handler' => $handler,
|
|
'middleware' => $middleware
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Match current request and execute middleware and controller action
|
|
*/
|
|
public function dispatch(Request $request, Response $response): void
|
|
{
|
|
$method = $request->getMethod();
|
|
$path = $request->getPath();
|
|
|
|
// Handle CORS Preflight Preemptively
|
|
if ($method === 'OPTIONS') {
|
|
$allowedOrigin = getenv('ALLOWED_ORIGIN') ?: '*';
|
|
$response->setHeader('Access-Control-Allow-Origin', $allowedOrigin);
|
|
$response->setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
$response->setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
|
|
$response->setHeader('Vary', 'Origin');
|
|
$response->setStatusCode(200);
|
|
exit;
|
|
}
|
|
|
|
foreach ($this->routes as $route) {
|
|
if ($route['method'] === $method && preg_match($route['pattern'], $path, $matches)) {
|
|
// Filter named captures from regex match
|
|
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
|
|
|
|
// Run global middleware first
|
|
foreach ($this->globalMiddleware as $mw) {
|
|
$mwInstance = new $mw();
|
|
$mwInstance->handle($request, $response);
|
|
}
|
|
|
|
// Run route specific middleware
|
|
foreach ($route['middleware'] as $mw) {
|
|
$mwInstance = new $mw();
|
|
$mwInstance->handle($request, $response);
|
|
}
|
|
|
|
// Execute Controller
|
|
$handler = $route['handler'];
|
|
if (is_array($handler) && count($handler) === 2) {
|
|
list($controllerClass, $action) = $handler;
|
|
if (class_exists($controllerClass)) {
|
|
$controller = new $controllerClass();
|
|
if (method_exists($controller, $action)) {
|
|
// Call action with Request, Response and URI dynamic parameters
|
|
call_user_func_array([$controller, $action], array_merge([$request, $response], $params));
|
|
return;
|
|
}
|
|
}
|
|
} elseif (is_callable($handler)) {
|
|
call_user_func_array($handler, array_merge([$request, $response], $params));
|
|
return;
|
|
}
|
|
|
|
error_log("Handler error for route: [{$method}] {$path}");
|
|
$response->error("Internal Server Error", 500);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Route not found
|
|
error_log("Route not found: [{$method}] {$path}");
|
|
$response->error("Not Found", 404);
|
|
}
|
|
}
|