139 lines
4.9 KiB
PHP
139 lines
4.9 KiB
PHP
<?php
|
||
/**
|
||
* ============================================
|
||
* File: Router.php
|
||
* Path: /app/Core/
|
||
* Purpose: Core router handling HTTP method–specific route dispatching with dynamic path and closure support.
|
||
* Version: 1.4
|
||
* Author: Wizdom Networks
|
||
* ============================================
|
||
*/
|
||
|
||
namespace WizdomNetworks\WizeWeb\Core;
|
||
|
||
use WizdomNetworks\WizeWeb\Utilities\Logger;
|
||
use WizdomNetworks\WizeWeb\Utilities\ErrorHandler;
|
||
use WizdomNetworks\WizeWeb\Core\View;
|
||
|
||
class Router
|
||
{
|
||
/**
|
||
* Array of registered routes indexed by HTTP method and path.
|
||
* @var array
|
||
*/
|
||
private array $routes = [];
|
||
|
||
/**
|
||
* Registers a controller-based route with optional path parameters.
|
||
*
|
||
* @param string $path The route path (e.g. "/contact" or "/verify/{code}").
|
||
* @param string $controller Fully qualified controller class.
|
||
* @param string $method Method in controller to invoke.
|
||
* @param string $httpMethod HTTP method (GET, POST, etc.). Defaults to GET.
|
||
*/
|
||
public function add(string $path, string $controller, string $method, string $httpMethod = 'GET'): void
|
||
{
|
||
$normalizedPath = trim($path, '/');
|
||
$routeKey = strtoupper($httpMethod) . ':' . $normalizedPath;
|
||
|
||
// Transform path into regex pattern and extract parameter names
|
||
$paramKeys = [];
|
||
$regexPath = preg_replace_callback('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', function ($matches) use (&$paramKeys) {
|
||
$paramKeys[] = $matches[1];
|
||
return '([^\/]+)';
|
||
}, $normalizedPath);
|
||
|
||
$this->routes[$routeKey] = [
|
||
'controller' => $controller,
|
||
'method' => $method,
|
||
'pattern' => "#^" . $regexPath . "$#",
|
||
'params' => $paramKeys
|
||
];
|
||
|
||
Logger::debug("Registering route: [$httpMethod] $path -> $controller::$method");
|
||
}
|
||
|
||
/**
|
||
* Registers a closure-based route.
|
||
*
|
||
* @param string $path The route path.
|
||
* @param \Closure $callback Anonymous function to handle the route.
|
||
* @param string $httpMethod HTTP method (GET, POST, etc.). Defaults to GET.
|
||
*/
|
||
public function addClosure(string $path, \Closure $callback, string $httpMethod = 'GET'): void
|
||
{
|
||
$routeKey = strtoupper($httpMethod) . ':' . trim($path, '/');
|
||
Logger::debug("Registering closure route: [$httpMethod] $path");
|
||
$this->routes[$routeKey] = $callback;
|
||
}
|
||
|
||
/**
|
||
* Dispatches the current request to the matching route or fallback to 404.
|
||
*
|
||
* @param string $path The requested path, usually from index.php.
|
||
*/
|
||
public function dispatch($path)
|
||
{
|
||
$httpMethod = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
|
||
$cleanPath = trim($path, '/');
|
||
$routeKeyBase = $httpMethod . ':';
|
||
|
||
foreach ($this->routes as $key => $route) {
|
||
if (strpos($key, $routeKeyBase) !== 0) {
|
||
continue;
|
||
}
|
||
|
||
// Handle closure route directly
|
||
if ($route instanceof \Closure && $key === $routeKeyBase . $cleanPath) {
|
||
Logger::info("Executing closure route: [$httpMethod] $cleanPath");
|
||
$route();
|
||
return;
|
||
}
|
||
|
||
// Only continue if route is an array
|
||
if (!is_array($route)) {
|
||
continue;
|
||
}
|
||
|
||
$routePattern = $route['pattern'] ?? null;
|
||
|
||
// Match dynamic route patterns and extract parameters
|
||
if ($routePattern && preg_match($routePattern, $cleanPath, $matches)) {
|
||
array_shift($matches); // Remove full match
|
||
$params = array_combine($route['params'], $matches) ?: [];
|
||
$controllerName = $route['controller'];
|
||
$method = $route['method'];
|
||
|
||
try {
|
||
if (!class_exists($controllerName)) {
|
||
throw new \Exception("Controller not found: $controllerName");
|
||
}
|
||
$controller = new $controllerName();
|
||
|
||
if (!method_exists($controller, $method)) {
|
||
throw new \Exception("Method $method not found in $controllerName");
|
||
}
|
||
|
||
Logger::info("Executing controller: $controllerName::$method with params: " . json_encode($params));
|
||
call_user_func_array([$controller, $method], $params);
|
||
return;
|
||
|
||
} catch (\Throwable $e) {
|
||
echo "<pre>";
|
||
echo "Exception: " . $e->getMessage() . "\n";
|
||
echo "File: " . $e->getFile() . "\n";
|
||
echo "Line: " . $e->getLine() . "\n";
|
||
echo "Trace:\n" . $e->getTraceAsString();
|
||
echo "</pre>";
|
||
exit;
|
||
}
|
||
}
|
||
}
|
||
|
||
// If no route matched, render 404 page
|
||
Logger::error("Route not found: [$httpMethod] $path");
|
||
http_response_code(404);
|
||
View::render('pages/404');
|
||
}
|
||
}
|