diff --git a/src/Fragments/Bundle/Controller/AbstractController.php b/src/Fragments/Bundle/Controller/AbstractController.php index d3b0f69..3ee5260 100644 --- a/src/Fragments/Bundle/Controller/AbstractController.php +++ b/src/Fragments/Bundle/Controller/AbstractController.php @@ -21,27 +21,32 @@ namespace Fragments\Bundle\Controller; -use Fragments\Component\Request; +use Fragments\Component\Http\Request; +use Fragments\Component\Http\Response; +use Fragments\Component\Http\RedirectResponse; use Fragments\Component\CsrfTokenManager; use Fragments\Component\Feedback; -use Fragments\Component\TemplateHelper; +use Fragments\Component\Templating; +use Fragments\Component\Routing\Router; abstract class AbstractController { - protected function renderTemplate(string $path, array $variables = []) { - $templateHelper = new TemplateHelper; - $templateHelper->render($path, $variables); + /** + * Conveniently render a template from the controllers. + */ + protected function render(string $template, array $variables = []): Response + { + $templating = new Templating(); + + return $templating->render($template, $variables); } - protected function isFormSubmitted(): bool + protected function redirectToRoute(string $routeId, $parameters = []): RedirectResponse { - $request = new Request; - - if ($request->requestMethod() == "POST") { - return true; - } + $router = new Router(); + $url = $router->generateUrl($routeId, $parameters); - return false; + return new RedirectResponse($url); } protected function isCsrfTokenValid(string $targetId, string $token): bool diff --git a/src/Fragments/Component/Bootstrap.php b/src/Fragments/Component/Bootstrap.php deleted file mode 100644 index abe8708..0000000 --- a/src/Fragments/Component/Bootstrap.php +++ /dev/null @@ -1,67 +0,0 @@ -. - */ - -namespace Fragments\Component; - -use Fragments\Component\Routing\Router; -use Fragments\Bundle\Exception\HttpException; -use Fragments\Component\TemplateHelper; - -class Bootstrap -{ - private $router; - - private $templateHelper; - - public function __construct() - { - $this->router = new Router; - $this->templateHelper = new TemplateHelper; - } - - public function run() - { - try { - $this->router->start(); - } catch (HttpException $error) { - $this->exceptionHandler($error); - } - } - - private function exceptionHandler(HttpException $error) - { - $statusCode = $error->getStatusCode(); - $message = $error->getMessage() . ' in file ' . $error->getFile() . ' at line ' . $error->getLine(); - - http_response_code($statusCode); - error_log($message); - - if (file_exists('../templates/error/' . $statusCode . '.php')) { - $this->templateHelper->render('../templates/error/' . $statusCode . '.php'); - } else { - $this->templateHelper->render('../templates/error/error.php', [ - 'statusCode' => $statusCode - ]); - } - - exit; - } -} \ No newline at end of file diff --git a/src/Fragments/Component/Http/RedirectResponse.php b/src/Fragments/Component/Http/RedirectResponse.php new file mode 100644 index 0000000..797d1fe --- /dev/null +++ b/src/Fragments/Component/Http/RedirectResponse.php @@ -0,0 +1,39 @@ +. + */ + +namespace Fragments\Component\Http; + +/** + * An object-oriented representation of the HTTP response. + */ +class RedirectResponse extends Response +{ + public function __construct(string $url) + { + $headers = []; + $content = ""; + + $content = "Redirecting to {$url}"; + $headers['Location'] = $url; + + parent::__construct($content, 302, $headers); + } +} \ No newline at end of file diff --git a/src/Fragments/Component/Http/Request.php b/src/Fragments/Component/Http/Request.php new file mode 100644 index 0000000..118b95e --- /dev/null +++ b/src/Fragments/Component/Http/Request.php @@ -0,0 +1,41 @@ +. + */ + +namespace Fragments\Component\Http; + +/** + * An object-oriented representation of the HTTP request. + */ +class Request +{ + public $post; + + public $get; + + public $server; + + public function __construct() + { + $this->post = $_POST; + $this->get = $_GET; + $this->server = $_SERVER; + } +} \ No newline at end of file diff --git a/src/Fragments/Component/Http/Response.php b/src/Fragments/Component/Http/Response.php new file mode 100644 index 0000000..adb8553 --- /dev/null +++ b/src/Fragments/Component/Http/Response.php @@ -0,0 +1,61 @@ +. + */ + +namespace Fragments\Component\Http; + +/** + * An object-oriented representation of the HTTP response. + */ +class Response +{ + public $content; + + public $statusCode; + + public $headers; + + public function __construct(string $content = '', int $statusCode = 200, array $headers = []) + { + $this->content = $content; + $this->statusCode = $statusCode; + $this->headers = $headers; + } + + public function send() + { + $this->sendHeaders(); + $this->sendContent(); + } + + private function sendHeaders() + { + foreach ($this->headers as $name => $value) { + header("{$name}: {$value}", true, $this->statusCode); + } + + http_response_code($this->statusCode); + } + + private function sendContent() + { + echo $this->content; + } +} \ No newline at end of file diff --git a/src/Fragments/Component/Request.php b/src/Fragments/Component/Request.php deleted file mode 100644 index 88648cc..0000000 --- a/src/Fragments/Component/Request.php +++ /dev/null @@ -1,81 +0,0 @@ -. - */ - -namespace Fragments\Component; - -use Fragments\Component\Routing\Router; - -/** - * Server Request Utility - * - * Manipulation or retrieval of information regarding HTTP requests. - */ -class Request -{ - public function getURI(): string - { - return $_SERVER['REQUEST_URI']; - } - - public function requestMethod(): string - { - return $_SERVER['REQUEST_METHOD']; - } - - public function post(string $value): ?string - { - if (array_key_exists($value, $_POST)) { - return $_POST[$value]; - } - - return null; - } - - public function get(string $value): ?string - { - if (array_key_exists($value, $_GET)) { - return $_GET[$value]; - } - - return null; - } - - /** - * Redirects the client to the specified path. - */ - public function redirect(string $path) - { - header('Location: ' . $path, true, 301); - exit; - } - - /** - * Generates a path from a route ID and redirects to it. - */ - public function redirectToRoute(string $routeId, array $parameters = []) - { - $router = new Router; - $url = $router->generateUrl($routeId, $parameters); - - header('Location: ' . $url, true, 301); - exit; - } -} diff --git a/src/Fragments/Component/Routing/Router.php b/src/Fragments/Component/Routing/Router.php index 29eda4e..e0be7f2 100644 --- a/src/Fragments/Component/Routing/Router.php +++ b/src/Fragments/Component/Routing/Router.php @@ -23,7 +23,8 @@ use Fragments\Component\Routing\Model\Route; use Fragments\Component\Routing\Parser\XMLParser; -use Fragments\Component\Request; +use Fragments\Component\Http\Request; +use Fragments\Component\Http\Response; use Fragments\Bundle\Exception\NotFoundHttpException; use Fragments\Bundle\Exception\MethodNotAllowedHttpException; use Fragments\Bundle\Exception\ServerErrorHttpException; @@ -32,30 +33,29 @@ class Router { private $parser; - private $request; - public function __construct() { $this->parser = new XMLParser; - $this->request = new Request; } - public function start() + public function run(Request $request): Response { - $route = $this->getMatchingRoute(); + $route = $this->getMatchingRoute($request); $controller = $route->getController(); $action = $route->getAction(); $parameters = $route->getParameters(); $controller = new $controller; - $controller->{$action}(...$parameters); + $response = $controller->{$action}(...$parameters); + + return $response; } - private function getMatchingRoute(): Route + private function getMatchingRoute(Request $request): Route { $routes = $this->parser->getRoutes(); - $uri = $this->request->getURI(); + $uri = $request->server['REQUEST_URI']; // Ignore GET parameters in the URI, if present if (strpos($uri, '?') !== false) { @@ -101,7 +101,7 @@ private function getMatchingRoute(): Route } } - if (!in_array($this->request->requestMethod(), $route->getMethods())) { + if (!in_array($request->server['REQUEST_METHOD'], $route->getMethods())) { throw new MethodNotAllowedHttpException; } diff --git a/src/Fragments/Component/TemplateHelper.php b/src/Fragments/Component/TemplateHelper.php deleted file mode 100644 index 192a657..0000000 --- a/src/Fragments/Component/TemplateHelper.php +++ /dev/null @@ -1,92 +0,0 @@ -. - */ - -namespace Fragments\Component; - -use Fragments\Component\Feedback; -use Fragments\Component\CsrfTokenManager; -use Fragments\Component\Request; -use Fragments\Component\Routing\Router; - -class TemplateHelper { - public function render(string $path, array $variables = []) - { - // Expose variables in the scope of the template - foreach ($variables as $name => $value) { - $$name = $value; - } - - require($path); - } - - public function getFeedback(): array - { - $feedback = new Feedback; - - return $feedback->get(); - } - - public function escape(string $value): string - { - return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); - } - - public function getCsrfToken(string $id): string - { - $csrfManager = new CsrfTokenManager; - $token = $csrfManager->getToken($id); - - return $token; - } - - public function getRequest(): Request - { - return new Request; - } - - public function isCurrentPage(string $path): bool - { - $request = new Request; - $uri = $request->getURI(); - - if ($path == $uri) { - return true; - } - - return false; - } - - public function generateUrl(string $routeId, array $parameters = []): string - { - $router = new Router(); - - return $router->generateUrl($routeId, $parameters); - } - - public function isAuthenticated(): bool - { - if (isset($_SESSION['user'])) { - return true; - } - - return false; - } -} \ No newline at end of file diff --git a/src/Fragments/Component/Templating.php b/src/Fragments/Component/Templating.php new file mode 100644 index 0000000..45cdd39 --- /dev/null +++ b/src/Fragments/Component/Templating.php @@ -0,0 +1,83 @@ +. + */ + +namespace Fragments\Component; + +use Fragments\Component\Http\Response; +use Fragments\Component\Http\Request; +use Fragments\Component\Routing\Router; +use Fragments\Component\Feedback; + +class Templating +{ + /** + * Constructs a Response object with the output of a template file. + */ + public function render(string $template, array $variables = []): Response + { + if (false === file_exists("../templates/{$template}")) { + throw new \Exception('The template file could not be found.'); + } + + ob_start(); + + $context = $this->getContext(); + extract($context, EXTR_PREFIX_ALL, 'app'); + extract($variables); + + include "../templates/{$template}"; + + return new Response(ob_get_clean()); + } + + /** + * Returns an array of variables that can be useful when extracted into the + * template's scope. + */ + private function getContext(): array + { + $context = []; + + if (isset($_SESSION['user'])) { + $context['user'] = $_SESSION['user']; + } else { + $context['user'] = []; + } + + $context['router'] = new Router(); + $context['feedback'] = new Feedback(); + $context['request'] = new Request(); + + return $context; + } + + public function escape(string $value): string + { + return htmlspecialchars($value, ENT_QUOTES); + } + + public function getCsrfToken(string $id): string + { + $csrfManager = new CsrfTokenManager; + + return $csrfManager->getToken($id); + } +} \ No newline at end of file