Add support for params in route with new route dispatcher

This commit is contained in:
Maarten 2024-11-26 13:38:35 +01:00
parent 0111ef9525
commit e8d8e0e95b
12 changed files with 393 additions and 115 deletions

View file

@ -0,0 +1,162 @@
<?php
namespace Core\Routing;
use Core\Env\Env;
use Core\Exceptions\Exceptions;
use Core\Exceptions\Exceptions\NotFoundHttpException;
use Core\Http\Request;
use Core\View\Render;
use Exception;
class RouteDispatcher
{
/**
* Current request instance
*
* @var \Core\Http\Request
*/
private Request $request;
/**
* Collection of all routes
*
* @var array
*/
private array $routeCollection;
/**
* @param \Core\Http\Request $request
* @param array $routeCollection
*/
public function __construct(Request $request, array $routeCollection)
{
$this->request = $request;
$this->routeCollection = $routeCollection;
try {
$route = $this->findMatchingRoute();
$controller = $this->instantiateController($route['controller']);
$action = $this->validateActionExists($controller, $route['action']);
$params = $this->resolveParameters($controller, $action, $route['params'] ?? []);
$this->executeAction($controller, $action, $params);
} catch (NotFoundHttpException $e) {
$this->handleException($e, 404, 'page not found');
} catch (Exception $e) {
$this->handleException($e, 500, 'something went wrong');
}
}
public static function dispatch(Request $request, array $routeCollection): void
{
new self($request, $routeCollection);
}
/**
* Locate a matching route for the incoming request.
*
* @return array
* @throws \Core\Exceptions\Exceptions\NotFoundHttpException
*/
private function findMatchingRoute(): array
{
$url = $this->request->url();
$method = $this->request->method();
foreach ($this->routeCollection[$method] ?? [] as $routeRegex => $route) {
if (preg_match($routeRegex, $url, $matches)) {
$params = array_filter(
$matches,
fn($key) => !is_numeric($key),
ARRAY_FILTER_USE_KEY
);
return array_merge($route, ['params' => $params]);
}
}
throw new NotFoundHttpException(sprintf("No route found for: %s", $url));
}
/**
* Create an instance of the requested controller.
*
* @param string $controllerName
* @return object
* @throws \Core\Exceptions\Exceptions\NotFoundHttpException
*/
private function instantiateController(string $controllerName): object
{
if (!class_exists($controllerName)) {
throw new NotFoundHttpException(sprintf("Controller '%s' missing", $controllerName));
}
return new $controllerName($this->request);
}
/**
* Validate that the action exists in the controller.
*
* @param object $controller
* @param string $actionName
* @return string
* @throws \Core\Exceptions\Exceptions\NotFoundHttpException
*/
private function validateActionExists(object $controller, string $actionName): string
{
if (!method_exists($controller, $actionName)) {
throw new NotFoundHttpException(sprintf("Method '%s' not found on '%s'", $actionName, get_class($controller)));
}
return $actionName;
}
/**
* Validate and resolve parameters for the controller action.
*
* @param object $controller
* @param string $action
* @param array $params
* @return array
* @throws \Exception
*/
private function resolveParameters(object $controller, string $action, array $params): array
{
return RouteValidator::resolve($controller, $action, $params);
}
/**
* Execute the resolved action on the controller with validated parameters.
*
* @param object $controller
* @param string $action
* @param array $params
* @return void
*/
private function executeAction(object $controller, string $action, array $params): void
{
$response = $controller->$action(...$params);
if ($response instanceof Render) {
$response->render();
}
}
/**
* Handle exceptions gracefully.
*
* @param Exception $e
* @param int $statusCode
* @param string $message
* @return void
*/
private function handleException(Exception $e, int $statusCode, string $message): void
{
if (Env::get('debug')) {
Exceptions::catchOne($e);
}
http_response_code($statusCode);
echo $message;
}
}