diff --git a/app/Application.php b/app/Application.php deleted file mode 100644 index 46d8c93..0000000 --- a/app/Application.php +++ /dev/null @@ -1,14 +0,0 @@ -response->view('subnet'); - } -} \ No newline at end of file diff --git a/composer.json b/composer.json index fcdd64b..15e4192 100644 --- a/composer.json +++ b/composer.json @@ -20,11 +20,7 @@ "psr-4": { "App\\": "app/", "Core\\": "src/" - }, - "files": [ - "src/Helpers/helpers.php" - ] - + } }, "config": { "optimize-autoloader": true diff --git a/config/routes.php b/config/routes.php index fd1f717..33b550b 100644 --- a/config/routes.php +++ b/config/routes.php @@ -2,10 +2,7 @@ use App\Controllers\Api\SubnetController; use App\Controllers\HomeController; -use App\Controllers\TestController; -use Core\Routing\Route; +use Core\Router\Router; -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 +Router::get('/', HomeController::class, 'index'); +Router::post('/api/subnet', SubnetController::class, 'data'); \ No newline at end of file diff --git a/public/index.php b/public/index.php index 978295c..a3c2da1 100644 --- a/public/index.php +++ b/public/index.php @@ -1,8 +1,15 @@ handle(); \ No newline at end of file +// Load routes +require '../config/routes.php'; + +// Load env +Env::load(); + +// Dispatch router +Router::dispatch(); \ No newline at end of file diff --git a/src/Bootstrap.php b/src/Bootstrap.php deleted file mode 100644 index f434d27..0000000 --- a/src/Bootstrap.php +++ /dev/null @@ -1,45 +0,0 @@ -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/Http/Controllers/Controller.php b/src/Controllers/Controller.php similarity index 93% rename from src/Http/Controllers/Controller.php rename to src/Controllers/Controller.php index d428fcd..e8f1cec 100644 --- a/src/Http/Controllers/Controller.php +++ b/src/Controllers/Controller.php @@ -1,6 +1,6 @@ pushHandler(self::handler()); - - self::$reporter = $reporter; - } - - return self::$reporter; - } - - /** - * Get correct handler - * - * @return \Whoops\Handler\Handler - */ - private static function handler(): Handler - { - if (env('debug')) { - if (request()?->is('post')) { - return new JsonResponseHandler(); - } - - return new PrettyPageHandler(); - } - - return new PlainTextHandler(); - } - - /** - * Catch all exceptions - * - * @return void - */ - public function register(): void - { - self::reporter()->register(); - } - - /** - * Catch single exception - * - * @param \Throwable $exception - * @return never - */ - public function handle(Throwable $exception): never - { - self::reporter()->handleException($exception); - - exit(0); - } - - /** - * Make new exception - * - * @param null $abstract - * @param string|null $message - * @return 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')) { - $this->handle($abstract); - } - - exit(0); - } - -} \ No newline at end of file diff --git a/src/Exceptions/Exceptions.php b/src/Exceptions/Exceptions.php new file mode 100644 index 0000000..ceb5f31 --- /dev/null +++ b/src/Exceptions/Exceptions.php @@ -0,0 +1,81 @@ +pushHandler(self::handler($request)); + + self::$instance = $instance; + } + + return self::$instance; + } + + /** + * Get correct handler + * + * @param \Core\Http\Request|null $request + * @return \Whoops\Handler\Handler + */ + private static function handler(Request|null $request): Handler + { + if (Env::get('debug')) { + if ($request?->is('post')) { + return new JsonResponseHandler(); + } + + return new PrettyPageHandler(); + } + + return new PlainTextHandler(); + } + + /** + * Catch all exceptions + * + * @param \Core\Http\Request $request + * @return void + */ + public static function catch(Request $request): void + { + self::instance($request)->register(); + } + + /** + * Catch single exception + * + * @param \Throwable $exception + * @return void + */ + public static function catchOne(Throwable $exception): void + { + self::instance()->handleException($exception); + } +} \ No newline at end of file diff --git a/src/Exceptions/Exceptions/ClassNotFoundException.php b/src/Exceptions/Exceptions/ClassNotFoundException.php deleted file mode 100644 index 494b2b4..0000000 --- a/src/Exceptions/Exceptions/ClassNotFoundException.php +++ /dev/null @@ -1,5 +0,0 @@ -request = app()->make(Request::class, [$_POST + $_FILES]); - - // Register exceptions handler for error reporting - app()->make(ExceptionHandler::class)->register(); - - // Load routes - require '../config/routes.php'; - - try { - // Boot application - app()->make(Application::class)->bootstrap(); - - // Dispatch router - app()->make(RouteDispatcher::class)->dispatch($this->request); - } catch (\Exception $e) { - exceptions()->handle($e); - } - } - - /** - * @param string $abstract - * @param mixed $arguments - * @return mixed - */ - public function make(string $abstract, mixed $arguments = []): mixed - { - return $this->resolve($abstract, $arguments); - } - - /** - * @param string $abstract - * @param mixed $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) { - exceptions()->handle($e); - } - } - - exceptions()->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 deleted file mode 100644 index 4475eb5..0000000 --- a/src/Helpers/helpers.php +++ /dev/null @@ -1,64 +0,0 @@ -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(); - } -} - -if (!function_exists('exceptions')) { - /** - * Get error handler instance - * - * @return \Core\Exceptions\ExceptionHandler - */ - function exceptions(): ExceptionHandler - { - return ExceptionHandler::instance(); - } -} diff --git a/src/Http/Request.php b/src/Http/Request.php index 7422c90..66bd9de 100644 --- a/src/Http/Request.php +++ b/src/Http/Request.php @@ -4,18 +4,6 @@ namespace Core\Http; class Request { - private array $data = []; - - /** - * Build request object - * - * @param array $data - */ - public function __construct(array $data) - { - $this->data = $data; - } - /** * Get request method * @@ -55,7 +43,7 @@ class Request */ public final function has(string $param): bool { - return isset($this->data[$param]); + return isset($_POST[$param]); } /** @@ -68,11 +56,11 @@ class Request public final function get(string|null $param = null, mixed $default = null): mixed { if($param == null) { - return $this->data; + return $_POST; } if($this->has($param)) { - return $this->data[$param]; + return $_POST[$param]; } return $default; diff --git a/src/Http/Response.php b/src/Http/Response.php index bb5d38d..99d4fcb 100644 --- a/src/Http/Response.php +++ b/src/Http/Response.php @@ -2,9 +2,9 @@ namespace Core\Http; -use Core\Http\View\Engine\HtmlEngine; -use Core\Http\View\Engine\JsonEngine; -use Core\Http\View\Render; +use Core\View\Render; +use Core\View\Render\HtmlRender; +use Core\View\Render\JsonRender; class Response { @@ -25,20 +25,20 @@ class Response * Render HTML * * @param string $view - * @return \Core\Http\View\Render + * @return \Core\View\Render */ public function view(string $view): Render { - return (new HtmlEngine())->view($view); + return (new HtmlRender())->view($view); } /** * Render JSON * - * @return \Core\Http\View\Render + * @return \Core\View\Render */ public function json(): Render { - return new JsonEngine(); + return new JsonRender(); } } \ No newline at end of file diff --git a/src/Router/Router.php b/src/Router/Router.php new file mode 100644 index 0000000..a8b57a2 --- /dev/null +++ b/src/Router/Router.php @@ -0,0 +1,109 @@ + $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/Route.php b/src/Routing/Route.php deleted file mode 100644 index 86c4a28..0000000 --- a/src/Routing/Route.php +++ /dev/null @@ -1,32 +0,0 @@ - $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/RouteDispatcher.php b/src/Routing/RouteDispatcher.php deleted file mode 100644 index 13dc276..0000000 --- a/src/Routing/RouteDispatcher.php +++ /dev/null @@ -1,144 +0,0 @@ -request = $request; - - 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'); - } - } - - /** - * 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(); - - $route = RouteCollection::find($method, $url); - if ($route) { - return $route; - } - - 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('debug')) { - exceptions()->handle($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 deleted file mode 100644 index e1139a6..0000000 --- a/src/Routing/RouteValidator.php +++ /dev/null @@ -1,89 +0,0 @@ -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 deleted file mode 100644 index 6d90c2a..0000000 --- a/src/Routing/Router.php +++ /dev/null @@ -1,22 +0,0 @@ -resourcePath('views/' . str_replace('.', '/', $this->view) . '.php'); + $viewsPath = $basePath . '/../resources/views/' . str_replace('.', '/', $this->view) . '.php'; if (file_exists($viewsPath)) { extract($this->data); diff --git a/src/Http/View/Engine/JsonEngine.php b/src/View/Render/JsonRender.php similarity index 70% rename from src/Http/View/Engine/JsonEngine.php rename to src/View/Render/JsonRender.php index f97d63c..3dc68b5 100644 --- a/src/Http/View/Engine/JsonEngine.php +++ b/src/View/Render/JsonRender.php @@ -1,11 +1,10 @@