From e8d8e0e95bffb89812343e074f1b05de0c541d55 Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 26 Nov 2024 13:38:35 +0100 Subject: [PATCH 1/9] Add support for params in route with new route dispatcher --- app/Controllers/TestController.php | 22 +++ config/routes.php | 7 +- public/index.php | 2 +- src/Exceptions/Exceptions.php | 2 + src/Exceptions/Exceptions/HttpException.php | 5 + .../Exceptions/InvalidArgumentException.php | 5 + .../Exceptions/NotFoundHttpException.php | 5 + src/Http/Request.php | 23 ++- src/Router/Router.php | 109 ------------ src/Routing/RouteDispatcher.php | 162 ++++++++++++++++++ src/Routing/RouteValidator.php | 89 ++++++++++ src/Routing/Router.php | 77 +++++++++ 12 files changed, 393 insertions(+), 115 deletions(-) create mode 100644 app/Controllers/TestController.php create mode 100644 src/Exceptions/Exceptions/HttpException.php create mode 100644 src/Exceptions/Exceptions/InvalidArgumentException.php create mode 100644 src/Exceptions/Exceptions/NotFoundHttpException.php delete mode 100644 src/Router/Router.php create mode 100644 src/Routing/RouteDispatcher.php create mode 100644 src/Routing/RouteValidator.php create mode 100644 src/Routing/Router.php diff --git a/app/Controllers/TestController.php b/app/Controllers/TestController.php new file mode 100644 index 0000000..8a944a4 --- /dev/null +++ b/app/Controllers/TestController.php @@ -0,0 +1,22 @@ +response->view('subnet'); + } +} \ No newline at end of file diff --git a/config/routes.php b/config/routes.php index 33b550b..659acde 100644 --- a/config/routes.php +++ b/config/routes.php @@ -2,7 +2,10 @@ use App\Controllers\Api\SubnetController; use App\Controllers\HomeController; -use Core\Router\Router; +use App\Controllers\TestController; +use Core\Routing\Router; Router::get('/', HomeController::class, 'index'); -Router::post('/api/subnet', SubnetController::class, 'data'); \ No newline at end of file +Router::post('/api/subnet', SubnetController::class, 'data'); +Router::get('/test/{id}', TestController::class, 'test'); +Router::get('/test/{id}/update', TestController::class, 'test'); \ No newline at end of file diff --git a/public/index.php b/public/index.php index a3c2da1..2bc027d 100644 --- a/public/index.php +++ b/public/index.php @@ -1,7 +1,7 @@ handleException($exception); + + exit(0); } } \ No newline at end of file diff --git a/src/Exceptions/Exceptions/HttpException.php b/src/Exceptions/Exceptions/HttpException.php new file mode 100644 index 0000000..833aeeb --- /dev/null +++ b/src/Exceptions/Exceptions/HttpException.php @@ -0,0 +1,5 @@ +data = $data; + + // Capture all exceptions + Exceptions::catch($this); + } + /** * Get request method * @@ -43,7 +60,7 @@ class Request */ public final function has(string $param): bool { - return isset($_POST[$param]); + return isset($this->data[$param]); } /** @@ -56,11 +73,11 @@ class Request public final function get(string|null $param = null, mixed $default = null): mixed { if($param == null) { - return $_POST; + return $this->data; } if($this->has($param)) { - return $_POST[$param]; + return $this->data[$param]; } return $default; diff --git a/src/Router/Router.php b/src/Router/Router.php deleted file mode 100644 index a8b57a2..0000000 --- a/src/Router/Router.php +++ /dev/null @@ -1,109 +0,0 @@ - $controller, - 'action' => $action, - ]; - } - - /** - * Dispatch router and run application - * - * @return void - */ - public static function dispatch(): void - { - // Init request - $request = new Request(); - $url = $request->url(); - $method = $request->method(); - - // Capture all exceptions - Exceptions::catch($request); - - try { - if (array_key_exists($url, self::$routes[$method])) { - $controllerName = self::$routes[$method][$url]['controller']; - $actionName = self::$routes[$method][$url]['action']; - - // Check for controller existence - if (!class_exists($controllerName)) { - throw new Exception(sprintf("Controller '%s' missing", $controllerName)); - } - - // Create controller object - $controller = new $controllerName($request); - - // Check for method on controller - if (!method_exists($controller, $actionName)) { - throw new Exception(sprintf("Method '%s' not found on '%s'", $actionName, $controllerName)); - } - - // Build response object of action - $response = $controller->$actionName(); - - // Render action - if ($response instanceof Render) { - $response->render(); - } - } else { - throw new Exception(sprintf("No route found for: %s", $url)); - } - } catch (Exception $e) { - Exceptions::catchOne($e); - } - } -} \ No newline at end of file diff --git a/src/Routing/RouteDispatcher.php b/src/Routing/RouteDispatcher.php new file mode 100644 index 0000000..3ca7782 --- /dev/null +++ b/src/Routing/RouteDispatcher.php @@ -0,0 +1,162 @@ +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; + } +} \ No newline at end of file diff --git a/src/Routing/RouteValidator.php b/src/Routing/RouteValidator.php new file mode 100644 index 0000000..2852407 --- /dev/null +++ b/src/Routing/RouteValidator.php @@ -0,0 +1,89 @@ +getParameters(); + $validatedParams = []; + + foreach ($methodParameters as $methodParameter) { + $paramName = $methodParameter->getName(); + $paramType = $methodParameter->getType(); + $isOptional = $methodParameter->isOptional(); + $defaultValue = $isOptional ? $methodParameter->getDefaultValue() : null; + + if (!array_key_exists($paramName, $params)) { + if ($isOptional) { + $validatedParams[$paramName] = $defaultValue; + continue; + } + throw new NotFoundHttpException(sprintf("Missing parameter '%s' for action '%s'", $paramName, $action)); + } + + $value = $params[$paramName]; + $validatedParams[$paramName] = self::validateType($paramName, $value, $paramType, $action); + } + + return $validatedParams; + } + + /** + * Validate and cast a parameter based on its type. + * + * @param string $paramName + * @param mixed $value + * @param ReflectionNamedType|null $paramType + * @param string $action + * @return mixed + * @throws \Core\Exceptions\Exceptions\NotFoundHttpException + */ + private static function validateType(string $paramName, mixed $value, ?ReflectionNamedType $paramType, string $action): mixed + { + if (!$paramType || $paramType->allowsNull() && $value === null) { + return $value; // No type or allows null, return as-is. + } + + $typeName = $paramType->getName(); + + switch ($typeName) { + case 'int': + if (!ctype_digit((string)$value)) { + throw new NotFoundHttpException(sprintf("Invalid type for parameter '%s'. Expected integer, got '%s'", $paramName, gettype($value))); + } + return (int)$value; + + case 'float': + if (!is_numeric($value)) { + throw new NotFoundHttpException(sprintf("Invalid type for parameter '%s'. Expected float, got '%s'", $paramName, gettype($value))); + } + return (float)$value; + + case 'string': + if (!is_string($value)) { + throw new NotFoundHttpException(sprintf("Invalid type for parameter '%s'. Expected string, got '%s'", $paramName, gettype($value))); + } + return $value; + + default: + throw new NotFoundHttpException(sprintf("Unsupported type '%s' for parameter '%s'", $typeName, $paramName)); + } + } +} \ No newline at end of file diff --git a/src/Routing/Router.php b/src/Routing/Router.php new file mode 100644 index 0000000..e88c095 --- /dev/null +++ b/src/Routing/Router.php @@ -0,0 +1,77 @@ +[a-zA-Z0-9_-]+)', $route); + $routeRegex = '#^' . $routeRegex . '$#'; + + self::$routes[$method][$routeRegex] = [ + 'controller' => $controller, + 'action' => $action, + 'original' => $route, + ]; + } + + /** + * Dispatch router and run application + * + * @return void + */ + public static function dispatch(): void + { + // Create request + $request = new Request($_POST + $_FILES); + + // Dispatch router + RouteDispatcher::dispatch($request, self::$routes); + } +} \ No newline at end of file From d0760ed95cf8515a51248e6e936bb2b8d043652a Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 26 Nov 2024 13:44:53 +0100 Subject: [PATCH 2/9] Route collection --- config/routes.php | 10 ++-- src/Routing/Route.php | 32 ++++++++++++ src/Routing/RouteCollection.php | 90 +++++++++++++++++++++++++++++++++ src/Routing/Router.php | 57 +-------------------- 4 files changed, 128 insertions(+), 61 deletions(-) create mode 100644 src/Routing/Route.php create mode 100644 src/Routing/RouteCollection.php diff --git a/config/routes.php b/config/routes.php index 659acde..fd1f717 100644 --- a/config/routes.php +++ b/config/routes.php @@ -3,9 +3,9 @@ use App\Controllers\Api\SubnetController; use App\Controllers\HomeController; use App\Controllers\TestController; -use Core\Routing\Router; +use Core\Routing\Route; -Router::get('/', HomeController::class, 'index'); -Router::post('/api/subnet', SubnetController::class, 'data'); -Router::get('/test/{id}', TestController::class, 'test'); -Router::get('/test/{id}/update', TestController::class, 'test'); \ No newline at end of file +Route::get('/', HomeController::class, 'index'); +Route::post('/api/subnet', SubnetController::class, 'data'); +Route::get('/test/{id}', TestController::class, 'test'); +Route::get('/test/{id}/update', TestController::class, 'test'); \ No newline at end of file diff --git a/src/Routing/Route.php b/src/Routing/Route.php new file mode 100644 index 0000000..86c4a28 --- /dev/null +++ b/src/Routing/Route.php @@ -0,0 +1,32 @@ + $controller, + 'action' => $action, + 'original' => $route, + ]; + } + + /** + * Reveal the treasure trove of routes. + * + * @return array + */ + public static function retrieve(): array + { + return self::$routes; + } + + /** + * Convert a route pattern into a regex for matching requests. + * + * @param string $route + * @return string + */ + protected static function convertToRegex(string $route): string + { + $regex = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<\1>[a-zA-Z0-9_-]+)', $route); + return '#^' . $regex . '$#'; + } + + /** + * Retrieve a specific route by method and URL. + * + * @param string $method + * @param string $url + * @return array|null + */ + public static function find(string $method, string $url): ?array + { + foreach (self::$routes[$method] ?? [] as $routeRegex => $route) { + if (preg_match($routeRegex, $url, $matches)) { + $route['params'] = array_filter( + $matches, + fn($key) => !is_numeric($key), + ARRAY_FILTER_USE_KEY + ); + return $route; + } + } + + return null; + } + + /** + * Purge all routes from the collection. + * + * @return void + */ + public static function clear(): void + { + self::$routes = []; + } +} \ No newline at end of file diff --git a/src/Routing/Router.php b/src/Routing/Router.php index e88c095..6d90c2a 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -6,61 +6,6 @@ use Core\Http\Request; class Router { - /** - * List of routes - * - * @var array - */ - protected static array $routes = []; - - /** - * Add GET route - * - * @param string $route - * @param string $controller - * @param string $action - * @return void - */ - public static function get(string $route, string $controller, string $action): void - { - self::register($route, $controller, $action, "GET"); - } - - /** - * Add POST route - * - * @param string $route - * @param string $controller - * @param string $action - * @return void - */ - public static function post(string $route, string $controller, string $action): void - { - self::register($route, $controller, $action, "POST"); - } - - /** - * Register route - * - * @param string $route - * @param string $controller - * @param string $action - * @param string $method - * @return void - */ - public static function register(string $route, string $controller, string $action, string $method): void - { - // Convert route with parameters into regex - $routeRegex = preg_replace('/\{([a-zA-Z0-9_]+)\}/', '(?P<\1>[a-zA-Z0-9_-]+)', $route); - $routeRegex = '#^' . $routeRegex . '$#'; - - self::$routes[$method][$routeRegex] = [ - 'controller' => $controller, - 'action' => $action, - 'original' => $route, - ]; - } - /** * Dispatch router and run application * @@ -72,6 +17,6 @@ class Router $request = new Request($_POST + $_FILES); // Dispatch router - RouteDispatcher::dispatch($request, self::$routes); + RouteDispatcher::dispatch($request, RouteCollection::retrieve()); } } \ No newline at end of file From ab5fa6d69e6a0467337433050f2e8b31aeb3d807 Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 26 Nov 2024 13:48:38 +0100 Subject: [PATCH 3/9] Update route dispatcher to use new collection find method --- src/Routing/RouteDispatcher.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Routing/RouteDispatcher.php b/src/Routing/RouteDispatcher.php index 3ca7782..b700682 100644 --- a/src/Routing/RouteDispatcher.php +++ b/src/Routing/RouteDispatcher.php @@ -48,6 +48,13 @@ class RouteDispatcher } } + /** + * Dispatch the router dispatcher + * + * @param \Core\Http\Request $request + * @param array $routeCollection + * @return void + */ public static function dispatch(Request $request, array $routeCollection): void { new self($request, $routeCollection); @@ -64,15 +71,9 @@ class RouteDispatcher $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]); - } + $route = RouteCollection::find($method, $url); + if ($route) { + return $route; } throw new NotFoundHttpException(sprintf("No route found for: %s", $url)); From 83bc1a1661b70fe4d517b9c7bd55ccfb11ac4e95 Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 26 Nov 2024 14:20:46 +0100 Subject: [PATCH 4/9] Add bootstrap factory for framework loading --- app/Application.php | 14 +++ composer.json | 6 +- public/index.php | 13 +-- src/Bootstrap.php | 45 +++++++++ .../{Exceptions.php => ExceptionHandler.php} | 40 +++++--- .../Exceptions/ClassNotFoundException.php | 5 + src/Factory/BootstrapFactory.php | 94 +++++++++++++++++++ src/Helpers/helpers.php | 51 ++++++++++ src/Http/Request.php | 5 - src/Routing/RouteDispatcher.php | 24 ++--- src/View/Render/HtmlRender.php | 2 +- 11 files changed, 254 insertions(+), 45 deletions(-) create mode 100644 app/Application.php create mode 100644 src/Bootstrap.php rename src/Exceptions/{Exceptions.php => ExceptionHandler.php} (63%) create mode 100644 src/Exceptions/Exceptions/ClassNotFoundException.php create mode 100644 src/Factory/BootstrapFactory.php create mode 100644 src/Helpers/helpers.php diff --git a/app/Application.php b/app/Application.php new file mode 100644 index 0000000..46d8c93 --- /dev/null +++ b/app/Application.php @@ -0,0 +1,14 @@ +handle(); \ No newline at end of file diff --git a/src/Bootstrap.php b/src/Bootstrap.php new file mode 100644 index 0000000..f434d27 --- /dev/null +++ b/src/Bootstrap.php @@ -0,0 +1,45 @@ +run(); + + return Bootstrap::getInstance(); + } + + /** + * Get the instance + * + * @return \Core\Factory\BootstrapFactory + */ + public static function getInstance(): BootstrapFactory + { + return self::$instance; + } + + /** + * Create new factory instance + * + * @return \Core\Factory\BootstrapFactory + */ + protected function run(): BootstrapFactory + { + return new BootstrapFactory(); + } +} \ No newline at end of file diff --git a/src/Exceptions/Exceptions.php b/src/Exceptions/ExceptionHandler.php similarity index 63% rename from src/Exceptions/Exceptions.php rename to src/Exceptions/ExceptionHandler.php index ec0f033..77f5355 100644 --- a/src/Exceptions/Exceptions.php +++ b/src/Exceptions/ExceptionHandler.php @@ -2,7 +2,6 @@ namespace Core\Exceptions; -use Core\Env\Env; use Core\Http\Request; use Throwable; use Whoops\Handler\Handler; @@ -11,7 +10,7 @@ use Whoops\Handler\PlainTextHandler; use Whoops\Handler\PrettyPageHandler; use Whoops\Run as Whoops; -class Exceptions +class ExceptionHandler { /** * Exceptions handler instance @@ -41,13 +40,12 @@ class Exceptions /** * Get correct handler * - * @param \Core\Http\Request|null $request * @return \Whoops\Handler\Handler */ - private static function handler(Request|null $request): Handler + private static function handler(): Handler { - if (Env::get('debug')) { - if ($request?->is('post')) { + if (env('debug')) { + if (request()?->is('post')) { return new JsonResponseHandler(); } @@ -60,24 +58,44 @@ class Exceptions /** * Catch all exceptions * - * @param \Core\Http\Request $request * @return void */ - public static function catch(Request $request): void + public static function catch(): void { - self::instance($request)->register(); + self::instance()->register(); } /** * Catch single exception * * @param \Throwable $exception - * @return void + * @return never */ - public static function catchOne(Throwable $exception): void + public static function catchOne(Throwable $exception): never { self::instance()->handleException($exception); exit(0); } + + /** + * Make new exception + * + * @param null $abstract + * @param string|null $message + * @return never + */ + public static function make(mixed $abstract = null, string|null $message = null): never + { + if(is_string($abstract)) { + $abstract = app()->make($abstract, $message); + } + + if(is_subclass_of($abstract, 'Exception')) { + self::catchOne($abstract); + } + + exit(0); + } + } \ No newline at end of file diff --git a/src/Exceptions/Exceptions/ClassNotFoundException.php b/src/Exceptions/Exceptions/ClassNotFoundException.php new file mode 100644 index 0000000..494b2b4 --- /dev/null +++ b/src/Exceptions/Exceptions/ClassNotFoundException.php @@ -0,0 +1,5 @@ +request = app()->make(Request::class, [$_POST + $_FILES]); + + // Capture all exceptions + ExceptionHandler::catch(); + + // Load routes + require '../config/routes.php'; + + try { + // Boot application + app()->make(Application::class)->bootstrap(); + + // Dispatch router + app()->make(RouteDispatcher::class)->dispatch($this->request, RouteCollection::retrieve()); + } catch (\Exception $e) { + ExceptionHandler::catchOne($e); + } + } + + /** + * @param string $abstract + * @param array $arguments + * @return mixed + */ + public function make(string $abstract, mixed $arguments = []): mixed + { + return $this->resolve($abstract, $arguments); + } + + /** + * @param string $abstract + * @param array $arguments + * @return mixed|null + */ + private function resolve(string $abstract, mixed $arguments = []): mixed + { + if (class_exists($abstract)) { + try { + $reflection = new \ReflectionClass($abstract); + return $reflection->newInstanceArgs($arguments); + } catch (\ReflectionException $e) { + ExceptionHandler::catchOne($e); + } + } + + ExceptionHandler::make(ClassNotFoundException::class, sprintf("Class '%s' not found", $abstract)); + } + + /** + * Get the request instance + * + * @return \Core\Http\Request + */ + public function request(): Request + { + return $this->request; + } + + /** + * Get path to file/folder in resources folder + * + * @param string $path + * @return string + */ + public function resourcePath(string $path = ''): string + { + return "../resources/" . $path; + } +} \ No newline at end of file diff --git a/src/Helpers/helpers.php b/src/Helpers/helpers.php new file mode 100644 index 0000000..9d3c5c4 --- /dev/null +++ b/src/Helpers/helpers.php @@ -0,0 +1,51 @@ +make($abstract, $arguments); + } +} + +if(!function_exists('env')) +{ + /** + * Get env variable + * + * @param string $key + * @return bool + */ + function env(string $key): mixed + { + return Env::get($key); + } +} + +if (!function_exists('request')) { + /** + * Get the request instance + * + * @return \Core\Http\Request + */ + function request(): Request + { + return app()->request(); + } +} diff --git a/src/Http/Request.php b/src/Http/Request.php index 8bbe2d9..7422c90 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -2,8 +2,6 @@ namespace Core\Http; -use Core\Exceptions\Exceptions; - class Request { private array $data = []; @@ -16,9 +14,6 @@ class Request public function __construct(array $data) { $this->data = $data; - - // Capture all exceptions - Exceptions::catch($this); } /** diff --git a/src/Routing/RouteDispatcher.php b/src/Routing/RouteDispatcher.php index b700682..5ced2a4 100644 --- a/src/Routing/RouteDispatcher.php +++ b/src/Routing/RouteDispatcher.php @@ -2,8 +2,7 @@ namespace Core\Routing; -use Core\Env\Env; -use Core\Exceptions\Exceptions; +use Core\Exceptions\ExceptionHandler; use Core\Exceptions\Exceptions\NotFoundHttpException; use Core\Http\Request; use Core\View\Render; @@ -26,10 +25,13 @@ class RouteDispatcher private array $routeCollection; /** + * Dispatch the router dispatcher + * * @param \Core\Http\Request $request * @param array $routeCollection + * @return void */ - public function __construct(Request $request, array $routeCollection) + public function dispatch(Request $request, array $routeCollection): void { $this->request = $request; $this->routeCollection = $routeCollection; @@ -48,18 +50,6 @@ class RouteDispatcher } } - /** - * Dispatch the router dispatcher - * - * @param \Core\Http\Request $request - * @param array $routeCollection - * @return void - */ - public static function dispatch(Request $request, array $routeCollection): void - { - new self($request, $routeCollection); - } - /** * Locate a matching route for the incoming request. * @@ -153,8 +143,8 @@ class RouteDispatcher */ private function handleException(Exception $e, int $statusCode, string $message): void { - if (Env::get('debug')) { - Exceptions::catchOne($e); + if (env('debug')) { + ExceptionHandler::catchOne($e); } http_response_code($statusCode); diff --git a/src/View/Render/HtmlRender.php b/src/View/Render/HtmlRender.php index e0a53d7..2c9224d 100644 --- a/src/View/Render/HtmlRender.php +++ b/src/View/Render/HtmlRender.php @@ -13,7 +13,7 @@ class HtmlRender extends Render public function render(): void { $basePath = $_SERVER['DOCUMENT_ROOT']; - $viewsPath = $basePath . '/../resources/views/' . str_replace('.', '/', $this->view) . '.php'; + $viewsPath = app()->resourcePath('views/' . str_replace('.', '/', $this->view) . '.php'); if (file_exists($viewsPath)) { extract($this->data); From 591e0c194ddd85932356c0cae4d43fb01756f8c3 Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 26 Nov 2024 14:22:04 +0100 Subject: [PATCH 5/9] Refactor classes into Http structure --- app/Controllers/Api/SubnetController.php | 6 +++--- app/Controllers/HomeController.php | 6 +++--- app/Controllers/TestController.php | 6 +++--- src/{ => Http}/Controllers/Controller.php | 2 +- src/Http/Response.php | 14 +++++++------- .../View/Engine/HtmlEngine.php} | 6 +++--- .../View/Engine/JsonEngine.php} | 6 +++--- src/{ => Http}/View/Render.php | 2 +- src/Routing/RouteDispatcher.php | 2 +- 9 files changed, 25 insertions(+), 25 deletions(-) rename src/{ => Http}/Controllers/Controller.php (93%) rename src/{View/Render/HtmlRender.php => Http/View/Engine/HtmlEngine.php} (82%) rename src/{View/Render/JsonRender.php => Http/View/Engine/JsonEngine.php} (70%) rename src/{ => Http}/View/Render.php (97%) diff --git a/app/Controllers/Api/SubnetController.php b/app/Controllers/Api/SubnetController.php index 1c0abca..384e4a5 100644 --- a/app/Controllers/Api/SubnetController.php +++ b/app/Controllers/Api/SubnetController.php @@ -3,8 +3,8 @@ namespace App\Controllers\Api; use App\Services\Subnet; -use Core\Controllers\Controller; -use Core\View\Render; +use Core\Http\Controllers\Controller; +use Core\Http\View\Engine; use Exception; class SubnetController extends Controller @@ -12,7 +12,7 @@ class SubnetController extends Controller /** * Get all subnet data * - * @return \Core\View\Render + * @return \Core\Http\View\Render */ public function data(): Render { diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index 3ea8195..f3d50e9 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -2,15 +2,15 @@ namespace App\Controllers; -use Core\Controllers\Controller; -use Core\View\Render; +use Core\Http\Controllers\Controller; +use Core\Http\View\Engine; class HomeController extends Controller { /** * Render index * - * @return \Core\View\Render + * @return \Core\Http\View\Render */ public function index(): Render { diff --git a/app/Controllers/TestController.php b/app/Controllers/TestController.php index 8a944a4..28bb40b 100644 --- a/app/Controllers/TestController.php +++ b/app/Controllers/TestController.php @@ -2,15 +2,15 @@ namespace App\Controllers; -use Core\Controllers\Controller; -use Core\View\Render; +use Core\Http\Controllers\Controller; +use Core\Http\View\Engine; class TestController extends Controller { /** * Render index * - * @return \Core\View\Render + * @return \Core\Http\View\Render */ public function test(int $id): Render { diff --git a/src/Controllers/Controller.php b/src/Http/Controllers/Controller.php similarity index 93% rename from src/Controllers/Controller.php rename to src/Http/Controllers/Controller.php index e8f1cec..d428fcd 100644 --- a/src/Controllers/Controller.php +++ b/src/Http/Controllers/Controller.php @@ -1,6 +1,6 @@ view($view); + return (new HtmlEngine())->view($view); } /** * Render JSON * - * @return \Core\View\Render + * @return \Core\Http\View\Render */ public function json(): Render { - return new JsonRender(); + return new JsonEngine(); } } \ No newline at end of file diff --git a/src/View/Render/HtmlRender.php b/src/Http/View/Engine/HtmlEngine.php similarity index 82% rename from src/View/Render/HtmlRender.php rename to src/Http/View/Engine/HtmlEngine.php index 2c9224d..3e000a3 100644 --- a/src/View/Render/HtmlRender.php +++ b/src/Http/View/Engine/HtmlEngine.php @@ -1,10 +1,10 @@ Date: Tue, 26 Nov 2024 14:22:59 +0100 Subject: [PATCH 6/9] Fix class imports --- src/Http/Response.php | 2 +- src/Http/View/Engine/HtmlEngine.php | 2 +- src/Http/View/Engine/JsonEngine.php | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Http/Response.php b/src/Http/Response.php index 9050124..bb5d38d 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -2,9 +2,9 @@ namespace Core\Http; -use Core\Http\View\Engine; use Core\Http\View\Engine\HtmlEngine; use Core\Http\View\Engine\JsonEngine; +use Core\Http\View\Render; class Response { diff --git a/src/Http/View/Engine/HtmlEngine.php b/src/Http/View/Engine/HtmlEngine.php index 3e000a3..c094be6 100644 --- a/src/Http/View/Engine/HtmlEngine.php +++ b/src/Http/View/Engine/HtmlEngine.php @@ -2,7 +2,7 @@ namespace Core\Http\View\Engine; -use Core\Http\View\Engine; +use Core\Http\View\Render; class HtmlEngine extends Render { diff --git a/src/Http/View/Engine/JsonEngine.php b/src/Http/View/Engine/JsonEngine.php index a0455a2..f97d63c 100644 --- a/src/Http/View/Engine/JsonEngine.php +++ b/src/Http/View/Engine/JsonEngine.php @@ -2,7 +2,8 @@ namespace Core\Http\View\Engine; -use Core\Http\View\Engine; + +use Core\Http\View\Render; class JsonEngine extends Render { From 9b80b16380f7759093accc9717e258aa601a7729 Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 26 Nov 2024 16:58:07 +0100 Subject: [PATCH 7/9] CS --- app/Controllers/Api/SubnetController.php | 2 +- app/Controllers/HomeController.php | 2 +- app/Controllers/TestController.php | 2 +- src/Factory/BootstrapFactory.php | 3 +-- src/Routing/RouteDispatcher.php | 14 +++----------- 5 files changed, 7 insertions(+), 16 deletions(-) diff --git a/app/Controllers/Api/SubnetController.php b/app/Controllers/Api/SubnetController.php index 384e4a5..39c5f61 100644 --- a/app/Controllers/Api/SubnetController.php +++ b/app/Controllers/Api/SubnetController.php @@ -4,7 +4,7 @@ namespace App\Controllers\Api; use App\Services\Subnet; use Core\Http\Controllers\Controller; -use Core\Http\View\Engine; +use Core\Http\View\Render; use Exception; class SubnetController extends Controller diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index f3d50e9..0b247c8 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -3,7 +3,7 @@ namespace App\Controllers; use Core\Http\Controllers\Controller; -use Core\Http\View\Engine; +use Core\Http\View\Render; class HomeController extends Controller { diff --git a/app/Controllers/TestController.php b/app/Controllers/TestController.php index 28bb40b..c44005e 100644 --- a/app/Controllers/TestController.php +++ b/app/Controllers/TestController.php @@ -3,7 +3,7 @@ namespace App\Controllers; use Core\Http\Controllers\Controller; -use Core\Http\View\Engine; +use Core\Http\View\Render; class TestController extends Controller { diff --git a/src/Factory/BootstrapFactory.php b/src/Factory/BootstrapFactory.php index 293bc36..08b02b7 100644 --- a/src/Factory/BootstrapFactory.php +++ b/src/Factory/BootstrapFactory.php @@ -7,7 +7,6 @@ use Core\Env\Env; use Core\Exceptions\ExceptionHandler; use Core\Exceptions\Exceptions\ClassNotFoundException; use Core\Http\Request; -use Core\Routing\RouteCollection; use Core\Routing\RouteDispatcher; class BootstrapFactory @@ -36,7 +35,7 @@ class BootstrapFactory app()->make(Application::class)->bootstrap(); // Dispatch router - app()->make(RouteDispatcher::class)->dispatch($this->request, RouteCollection::retrieve()); + app()->make(RouteDispatcher::class)->dispatch($this->request); } catch (\Exception $e) { ExceptionHandler::catchOne($e); } diff --git a/src/Routing/RouteDispatcher.php b/src/Routing/RouteDispatcher.php index 9106021..7b8c320 100644 --- a/src/Routing/RouteDispatcher.php +++ b/src/Routing/RouteDispatcher.php @@ -6,6 +6,7 @@ use Core\Exceptions\ExceptionHandler; use Core\Exceptions\Exceptions\NotFoundHttpException; use Core\Http\Request; use Core\Http\View\Engine; +use Core\Http\View\Render; use Exception; class RouteDispatcher @@ -17,24 +18,15 @@ class RouteDispatcher */ private Request $request; - /** - * Collection of all routes - * - * @var array - */ - private array $routeCollection; - /** * Dispatch the router dispatcher * * @param \Core\Http\Request $request - * @param array $routeCollection * @return void */ - public function dispatch(Request $request, array $routeCollection): void + public function dispatch(Request $request): void { - $this->request = $request; - $this->routeCollection = $routeCollection; + $this->request = $request; try { $route = $this->findMatchingRoute(); From eb3e310b0a4daed11d0dd42f37bad19707226927 Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 26 Nov 2024 19:53:35 +0100 Subject: [PATCH 8/9] Update exception handler --- src/Exceptions/ExceptionHandler.php | 53 ++++++++++++++++++++--------- src/Factory/BootstrapFactory.php | 14 ++++---- src/Helpers/helpers.php | 17 +++++++-- src/Routing/RouteDispatcher.php | 3 +- 4 files changed, 59 insertions(+), 28 deletions(-) diff --git a/src/Exceptions/ExceptionHandler.php b/src/Exceptions/ExceptionHandler.php index 77f5355..29524fc 100644 --- a/src/Exceptions/ExceptionHandler.php +++ b/src/Exceptions/ExceptionHandler.php @@ -2,7 +2,6 @@ namespace Core\Exceptions; -use Core\Http\Request; use Throwable; use Whoops\Handler\Handler; use Whoops\Handler\JsonResponseHandler; @@ -13,30 +12,50 @@ use Whoops\Run as Whoops; class ExceptionHandler { /** - * Exceptions handler instance + * Exceptions reporter instance * * @var Whoops */ - private static Whoops $instance; + private static Whoops $reporter; /** - * Get exceptions handler instance + * Exceptions handler instance * - * @param \Core\Http\Request|null $request - * @return \Whoops\Run + * @var \Core\Exceptions\ExceptionHandler */ - public static function instance(Request|null $request = null): Whoops + private static ExceptionHandler $instance; + + /** + * Get instance of exceptions handler + * + * @return \Core\Exceptions\ExceptionHandler + */ + public static function instance(): ExceptionHandler { if (!isset(self::$instance)) { - $instance = new Whoops(); - $instance->pushHandler(self::handler($request)); - - self::$instance = $instance; + self::$instance = new self(); } return self::$instance; } + /** + * Get exceptions reporter instance + * + * @return \Whoops\Run + */ + public static function reporter(): Whoops + { + if (!isset(self::$reporter)) { + $reporter = new Whoops(); + $reporter->pushHandler(self::handler()); + + self::$reporter = $reporter; + } + + return self::$reporter; + } + /** * Get correct handler * @@ -60,9 +79,9 @@ class ExceptionHandler * * @return void */ - public static function catch(): void + public function register(): void { - self::instance()->register(); + self::reporter()->register(); } /** @@ -71,9 +90,9 @@ class ExceptionHandler * @param \Throwable $exception * @return never */ - public static function catchOne(Throwable $exception): never + public function handle(Throwable $exception): never { - self::instance()->handleException($exception); + self::reporter()->handleException($exception); exit(0); } @@ -85,14 +104,14 @@ class ExceptionHandler * @param string|null $message * @return never */ - public static function make(mixed $abstract = null, string|null $message = null): never + public function make(mixed $abstract = null, string|null $message = null): never { if(is_string($abstract)) { $abstract = app()->make($abstract, $message); } if(is_subclass_of($abstract, 'Exception')) { - self::catchOne($abstract); + $this->handle($abstract); } exit(0); diff --git a/src/Factory/BootstrapFactory.php b/src/Factory/BootstrapFactory.php index 08b02b7..fecf650 100644 --- a/src/Factory/BootstrapFactory.php +++ b/src/Factory/BootstrapFactory.php @@ -24,8 +24,8 @@ class BootstrapFactory // Create request $this->request = app()->make(Request::class, [$_POST + $_FILES]); - // Capture all exceptions - ExceptionHandler::catch(); + // Register exceptions handler for error reporting + app()->make(ExceptionHandler::class)->register(); // Load routes require '../config/routes.php'; @@ -37,13 +37,13 @@ class BootstrapFactory // Dispatch router app()->make(RouteDispatcher::class)->dispatch($this->request); } catch (\Exception $e) { - ExceptionHandler::catchOne($e); + exceptions()->handle($e); } } /** * @param string $abstract - * @param array $arguments + * @param mixed $arguments * @return mixed */ public function make(string $abstract, mixed $arguments = []): mixed @@ -53,7 +53,7 @@ class BootstrapFactory /** * @param string $abstract - * @param array $arguments + * @param mixed $arguments * @return mixed|null */ private function resolve(string $abstract, mixed $arguments = []): mixed @@ -63,11 +63,11 @@ class BootstrapFactory $reflection = new \ReflectionClass($abstract); return $reflection->newInstanceArgs($arguments); } catch (\ReflectionException $e) { - ExceptionHandler::catchOne($e); + exceptions()->handle($e); } } - ExceptionHandler::make(ClassNotFoundException::class, sprintf("Class '%s' not found", $abstract)); + exceptions()->make(ClassNotFoundException::class, sprintf("Class '%s' not found", $abstract)); } /** diff --git a/src/Helpers/helpers.php b/src/Helpers/helpers.php index 9d3c5c4..4475eb5 100644 --- a/src/Helpers/helpers.php +++ b/src/Helpers/helpers.php @@ -2,6 +2,7 @@ use Core\Bootstrap; use Core\Env\Env; +use Core\Exceptions\ExceptionHandler; use Core\Http\Request; @@ -11,10 +12,10 @@ if(!function_exists('app')) * Make abstract of instance or get application instance * * @param mixed $abstract - * @param array $arguments + * @param mixed $arguments * @return \Core\Factory\BootstrapFactory|mixed */ - function app(mixed $abstract = null, array $arguments = []): mixed + function app(mixed $abstract = null, mixed $arguments = []): mixed { if (is_null($abstract)) { return Bootstrap::getInstance(); @@ -49,3 +50,15 @@ if (!function_exists('request')) { return app()->request(); } } + +if (!function_exists('exceptions')) { + /** + * Get error handler instance + * + * @return \Core\Exceptions\ExceptionHandler + */ + function exceptions(): ExceptionHandler + { + return ExceptionHandler::instance(); + } +} diff --git a/src/Routing/RouteDispatcher.php b/src/Routing/RouteDispatcher.php index 7b8c320..13dc276 100644 --- a/src/Routing/RouteDispatcher.php +++ b/src/Routing/RouteDispatcher.php @@ -5,7 +5,6 @@ namespace Core\Routing; use Core\Exceptions\ExceptionHandler; use Core\Exceptions\Exceptions\NotFoundHttpException; use Core\Http\Request; -use Core\Http\View\Engine; use Core\Http\View\Render; use Exception; @@ -136,7 +135,7 @@ class RouteDispatcher private function handleException(Exception $e, int $statusCode, string $message): void { if (env('debug')) { - ExceptionHandler::catchOne($e); + exceptions()->handle($e); } http_response_code($statusCode); From 9566ee3eadfd4fbc593903abdf1a72d6e5707149 Mon Sep 17 00:00:00 2001 From: Maarten Date: Tue, 26 Nov 2024 19:55:58 +0100 Subject: [PATCH 9/9] CS --- src/Routing/RouteValidator.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Routing/RouteValidator.php b/src/Routing/RouteValidator.php index 2852407..e1139a6 100644 --- a/src/Routing/RouteValidator.php +++ b/src/Routing/RouteValidator.php @@ -20,14 +20,14 @@ class RouteValidator */ public static function resolve(object $controller, string $action, array $params): array { - $reflection = new ReflectionMethod($controller, $action); + $reflection = new ReflectionMethod($controller, $action); $methodParameters = $reflection->getParameters(); - $validatedParams = []; + $validatedParams = []; foreach ($methodParameters as $methodParameter) { - $paramName = $methodParameter->getName(); - $paramType = $methodParameter->getType(); - $isOptional = $methodParameter->isOptional(); + $paramName = $methodParameter->getName(); + $paramType = $methodParameter->getType(); + $isOptional = $methodParameter->isOptional(); $defaultValue = $isOptional ? $methodParameter->getDefaultValue() : null; if (!array_key_exists($paramName, $params)) { @@ -38,7 +38,7 @@ class RouteValidator throw new NotFoundHttpException(sprintf("Missing parameter '%s' for action '%s'", $paramName, $action)); } - $value = $params[$paramName]; + $value = $params[$paramName]; $validatedParams[$paramName] = self::validateType($paramName, $value, $paramType, $action); }