diff --git a/README.md b/README.md index 32b3d41..3f16215 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Fragments aims to be a small PHP framework for web applications. Keep in mind th It has its own router component and is heavily inspired by [Symfony](https://symfony.com/). ## Requirements +- PHP 8 - [Composer](https://getcomposer.org/) - PHP XML extension. This package is called `php-xml` on Ubuntu. diff --git a/src/Fragments/Component/Bootstrap.php b/src/Fragments/Component/Bootstrap.php index 1258a51..551ec54 100644 --- a/src/Fragments/Component/Bootstrap.php +++ b/src/Fragments/Component/Bootstrap.php @@ -43,13 +43,8 @@ public function processRequest(Request $request): Response { try { $route = $this->router->getRouteFromRequest($request); - - $controller = $route->getController(); - $action = $route->getAction(); - $parameters = $route->getParameters(); - - $controller = new $controller; - $response = $controller->{$action}(...$parameters); + $controller = new $route->controller; + $response = $controller->{$route->action}(...$route->parameters); } catch (HttpException $exception) { $response = $this->createCustomErrorResponse($exception); } catch (\Throwable $exception) { @@ -61,23 +56,23 @@ public function processRequest(Request $request): Response private function createCustomErrorResponse(HttpException $exception): Response { - if ($exception->getStatusCode() === 500) { + if ($exception->statusCode === 500) { error_log($exception); } if (file_exists('../templates/error')) { - if (file_exists("../templates/error/{$exception->getStatusCode()}.php")) { - $response = $this->templating->render("error/{$exception->getStatusCode()}.php"); - $response->setStatusCode($exception->getStatusCode()); + if (file_exists("../templates/error/{$exception->statusCode}.php")) { + $response = $this->templating->render("error/{$exception->statusCode}.php"); + $response->statusCode = $exception->statusCode; } else { $response = $this->templating->render('error/error.php', [ - 'statusCode' => $exception->getStatusCode() + 'statusCode' => $exception->statusCode ]); - $response->setStatusCode($exception->getStatusCode()); + $response->statusCode = $exception->statusCode; } } else { - $response = new Response($exception->getMessage(), $exception->getStatusCode()); + $response = new Response(content: $exception->getMessage(), statusCode: $exception->statusCode); } return $response; @@ -92,17 +87,17 @@ private function createServerErrorResponse(\Throwable $exception): Response if (file_exists("../templates/error/500.php")) { // Render code-specific template $response = $this->templating->render("error/500.php"); - $response->setStatusCode(500); + $response->statusCode = 500; } else { // Render generic template $response = $this->templating->render('error/error.php', [ 'statusCode' => 500 ]); - $response->setStatusCode(500); + $response->statusCode = 500; } } else { - $response = new Response('Something went wrong.', 500); + $response = new Response(content: 'Something went wrong.', statusCode: 500); } return $response; diff --git a/src/Fragments/Component/Html/Templating.php b/src/Fragments/Component/Html/Templating.php index fc6f0b3..aa3003e 100644 --- a/src/Fragments/Component/Html/Templating.php +++ b/src/Fragments/Component/Html/Templating.php @@ -66,9 +66,9 @@ private function buildContext(): array return $context; } - public function escape(string $value): string + public function escape(string $string): string { - return htmlspecialchars($value, ENT_QUOTES); + return htmlspecialchars($string, flags: ENT_QUOTES); } public function getCsrfToken(string $name): string diff --git a/src/Fragments/Component/Http/Exception/HttpException.php b/src/Fragments/Component/Http/Exception/HttpException.php index a597b51..e9d74b5 100644 --- a/src/Fragments/Component/Http/Exception/HttpException.php +++ b/src/Fragments/Component/Http/Exception/HttpException.php @@ -23,7 +23,7 @@ class HttpException extends \RuntimeException { - private $statusCode; + public int $statusCode; public function __construct(int $statusCode, string $message = '', \Throwable $previous = null, ?int $code = 0) { @@ -31,9 +31,4 @@ public function __construct(int $statusCode, string $message = '', \Throwable $p parent::__construct($message, $code, $previous); } - - public function getStatusCode(): int - { - return $this->statusCode; - } } \ No newline at end of file diff --git a/src/Fragments/Component/Http/RedirectResponse.php b/src/Fragments/Component/Http/RedirectResponse.php index 99186ee..0c7981e 100644 --- a/src/Fragments/Component/Http/RedirectResponse.php +++ b/src/Fragments/Component/Http/RedirectResponse.php @@ -22,7 +22,7 @@ namespace Fragments\Component\Http; /** - * An object-oriented representation of the HTTP response. + * Conveniently performs a redirection to the specified URL. */ class RedirectResponse extends Response { diff --git a/src/Fragments/Component/Http/Request.php b/src/Fragments/Component/Http/Request.php index 09cc293..aa1d19d 100644 --- a/src/Fragments/Component/Http/Request.php +++ b/src/Fragments/Component/Http/Request.php @@ -26,11 +26,11 @@ */ class Request { - public $post; + public array $post; - public $get; + public array $get; - public $server; + public array $server; public function __construct() { diff --git a/src/Fragments/Component/Http/Response.php b/src/Fragments/Component/Http/Response.php index db7d6d3..33bc85c 100644 --- a/src/Fragments/Component/Http/Response.php +++ b/src/Fragments/Component/Http/Response.php @@ -26,11 +26,11 @@ */ class Response { - public $content; + public string $content; - public $statusCode; + public int $statusCode; - public $headers; + public array $headers; public function __construct(string $content = '', int $statusCode = 200, array $headers = []) { @@ -44,11 +44,6 @@ public function send() $this->sendHeaders(); $this->sendContent(); } - - public function setStatusCode(int $statusCode) - { - $this->statusCode = $statusCode; - } private function sendHeaders() { diff --git a/src/Fragments/Component/Routing/Model/Route.php b/src/Fragments/Component/Routing/Model/Route.php index ecb8759..71d7374 100644 --- a/src/Fragments/Component/Routing/Model/Route.php +++ b/src/Fragments/Component/Routing/Model/Route.php @@ -26,22 +26,22 @@ class Route /** * The route identifier. */ - private $id; + public string $id; /** * The associated URL. */ - private $path; + public string $path; /** * The fully qualified class name of the controller. */ - private $controller; + public string $controller; /** * The class method to be executed. */ - private $action; + public string $action; /** * The request methods supported. @@ -51,55 +51,7 @@ class Route /** * Route parameters injected by the router. */ - private $parameters = []; - - public function getId(): string - { - return $this->id; - } - - public function setId(string $id): self - { - $this->id = $id; - - return $this; - } - - public function getPath(): string - { - return $this->path; - } - - public function setPath(string $path): self - { - $this->path = $path; - - return $this; - } - - public function getController(): string - { - return $this->controller; - } - - public function setController(string $controller): self - { - $this->controller = $controller; - - return $this; - } - - public function getAction(): string - { - return $this->action; - } - - public function setAction(string $action): self - { - $this->action = $action; - - return $this; - } + public array $parameters = []; public function getMethods(): array { @@ -114,16 +66,4 @@ public function setMethods(string $methods): self return $this; } - - public function getParameters(): array - { - return $this->parameters; - } - - public function setParameters(array $parameters): self - { - $this->parameters = $parameters; - - return $this; - } } diff --git a/src/Fragments/Component/Routing/Parser/XMLParser.php b/src/Fragments/Component/Routing/Parser/XMLParser.php index 0cd5f7e..65bb46f 100644 --- a/src/Fragments/Component/Routing/Parser/XMLParser.php +++ b/src/Fragments/Component/Routing/Parser/XMLParser.php @@ -36,11 +36,11 @@ public function getRoutes(): array foreach ($file as $entry) { $route = new Route; - $route->setId($entry->id); - $route->setPath($entry->path); + $route->id = $entry->id; + $route->path = $entry->path; + $route->controller = $entry->controller; + $route->action = $entry->action; $route->setMethods($entry->methods); - $route->setController($entry->controller); - $route->setAction($entry->action); $routes[] = $route; } diff --git a/src/Fragments/Component/Routing/Router.php b/src/Fragments/Component/Routing/Router.php index 6d79585..ce5bbd5 100644 --- a/src/Fragments/Component/Routing/Router.php +++ b/src/Fragments/Component/Routing/Router.php @@ -41,8 +41,8 @@ public function getRouteFromRequest(Request $request): Route $uri = $request->server['REQUEST_URI']; // Ignore GET parameters in the URI, if present - if (strpos($uri, '?') !== false) { - $uri = strstr($uri, '?', true); + if (strpos(haystack: $uri, needle: '?') !== false) { + $uri = strstr(haystack: $uri, needle: '?', before_needle: true); } // Eliminate the trailing forward slash from the URI @@ -51,16 +51,14 @@ public function getRouteFromRequest(Request $request): Route } foreach ($routes as $route) { - if ($route->getPath() != $uri) { - $routePath = $route->getPath(); - + if ($route->path !== $uri) { // Are there any wildcards in the route path? - if (!preg_match('/{(\w+)}/', $routePath)) { + if (false == preg_match('/{(\w+)}/', $route->path)) { continue; } // Replace all wildcards with capturing groups - $regex = preg_replace('/{(\w+)}/', '(\w+)', $routePath); + $regex = preg_replace('/{(\w+)}/', '(\w+)', $route->path); // Escape forward slashes in the path $regex = preg_replace('/\//', '\/', $regex); @@ -68,7 +66,7 @@ public function getRouteFromRequest(Request $request): Route // Add start and end regex delimiters $regex = '/^' . $regex . '$/'; - if (preg_match($regex, $uri, $matches)) { + if (preg_match(pattern: $regex, subject: $uri, matches: $matches)) { // The first item is not a wildcard value, so remove it array_shift($matches); @@ -78,20 +76,20 @@ public function getRouteFromRequest(Request $request): Route $parameters[] = $parameter; } - $route->setParameters($parameters); + $route->parameters = $parameters; } else { continue; } } - if (!in_array($request->server['REQUEST_METHOD'], $route->getMethods())) { - throw new HttpException(405, 'Method not allowed.'); + if (false === in_array(needle: $request->server['REQUEST_METHOD'], haystack: $route->getMethods())) { + throw new HttpException(statusCode: 405, message: 'Method not allowed.'); } return $route; } - throw new HttpException(404, 'Route not found.'); + throw new HttpException(statusCode: 404, message: 'Route not found.'); } private function getRouteById(string $routeId): Route @@ -99,26 +97,26 @@ private function getRouteById(string $routeId): Route $routes = $this->parser->getRoutes(); foreach ($routes as $route) { - if ($route->getId() == $routeId) { + if ($route->id === $routeId) { return $route; } } - throw new HttpException(404, 'Route not found.'); + throw new HttpException(statusCode: 404, message: 'Route not found.'); } public function generateUrl(string $routeId, array $parameters = []): string { $route = $this->getRouteById($routeId); - $routePath = $route->getPath(); + $routePath = $route->path; // If there are no wildcards in this route path, return it as is - if (!preg_match('/{(\w+)}/', $routePath)) { + if (false == preg_match('/{(\w+)}/', $routePath)) { return $routePath; } // Break the route path in segments, without forward slashes - $routePath = explode('/', trim($routePath, '/')); + $routePath = explode(delimiter: '/', string: trim($routePath, '/')); /* * Iterate over the parameters, trying to find a corresponding wildcard @@ -127,12 +125,12 @@ public function generateUrl(string $routeId, array $parameters = []): string */ foreach ($parameters as $parameterName => $parameterValue) { foreach ($routePath as $pathKey => $pathSegment) { - $routePath[$pathKey] = preg_replace('/{' . $parameterName . '}/', $parameterValue, $pathSegment); + $routePath[$pathKey] = preg_replace(pattern: '/{' . $parameterName . '}/', replacement: $parameterValue, subject: $pathSegment); } } // Rebuild the path as a string, restoring forward slashes - $routePath = '/' . implode('/', $routePath); + $routePath = '/' . implode(glue: '/', pieces: $routePath); if (preg_match('/{(\w+)}/', $routePath)) { throw new \RuntimeException('Failed to generate URL due to missing or invalid parameters: ' . $routePath); diff --git a/src/Fragments/Component/Session/Csrf.php b/src/Fragments/Component/Session/Csrf.php index 14f44b6..8327d03 100644 --- a/src/Fragments/Component/Session/Csrf.php +++ b/src/Fragments/Component/Session/Csrf.php @@ -47,7 +47,7 @@ public function get(string $name): string return $bag[$name]; } - $token = bin2hex(random_bytes(32)); + $token = bin2hex(random_bytes(length: 32)); $bag[$name] = $token; $this->session->set(self::BAG_NAME, $bag); @@ -61,7 +61,7 @@ public function verify(string $name, string $token): bool $bag = $this->session->get(self::BAG_NAME); if (false === array_key_exists($name, $bag)) { - throw new HttpException(403, 'The CSRF token identifier could not be found.'); + throw new HttpException(statusCode: 403, message: 'The CSRF token identifier could not be found.'); } return hash_equals($bag[$name], $token); diff --git a/src/Fragments/Component/Session/Session.php b/src/Fragments/Component/Session/Session.php index 1e3103f..0beb3ce 100644 --- a/src/Fragments/Component/Session/Session.php +++ b/src/Fragments/Component/Session/Session.php @@ -30,16 +30,13 @@ public function start(bool $readAndClose = false): bool $options = [ 'cookie_httponly' => 1, 'use_strict_mode' => 1, + 'cookie_samesite' => 'Lax' ]; if ($readAndClose) { $options['read_and_close'] = true; } - if (version_compare(phpversion(), '7.3.0', '>')) { - $options['cookie_samesite'] = 'Lax'; - } - $request = new Request(); if ($request->isSecure()) {