From 87a1e2e76b800e3c012cd172e37aa89cf16b4d77 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Wed, 8 Oct 2025 15:43:39 -0700 Subject: [PATCH] Add and use `WebResponse` class Represents the response to a request, holding the raw content to show, the headers to send, and the response code to use. This allows actually responding with a 404 or 405 when needed. Also add new `AbstractPage` base class, and have the non-UI pages (RedirectPage, RobotsTxtPage) extend that. --- index.php | 2 +- src/Pages/AbstractPage.php | 16 ++++++++++++++++ src/Pages/BasePage.php | 17 ++++++++++++++++- src/Pages/BlogPostPage.php | 1 + src/Pages/Error404Page.php | 1 + src/Pages/Error405Page.php | 1 + src/Pages/RedirectPage.php | 18 ++++++++---------- src/Pages/RobotsTxtPage.php | 15 +++++++++------ src/Pages/ToolPage.php | 1 + src/Router.php | 4 ++-- src/WebResponse.php | 30 ++++++++++++++++++++++++++++++ tests/StaticOutputTest.php | 16 ++++++++++------ 12 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 src/Pages/AbstractPage.php create mode 100644 src/WebResponse.php diff --git a/index.php b/index.php index e9a4402..f7d5310 100644 --- a/index.php +++ b/index.php @@ -10,4 +10,4 @@ $_SERVER['REQUEST_URI'] ); -echo $page->getPageOutput(); +$page->getResponse()->applyResponse(); diff --git a/src/Pages/AbstractPage.php b/src/Pages/AbstractPage.php new file mode 100644 index 0000000..115f0d4 --- /dev/null +++ b/src/Pages/AbstractPage.php @@ -0,0 +1,16 @@ +head = FluentHTML::fromTag( 'head' ); $this->contentWrapper = FluentHTML::fromTag( 'div' ) @@ -79,6 +82,10 @@ protected function addScript( string $fileName ): void { ); } + protected function setResponseCode( int $code ): void { + $this->responseCode = $code; + } + abstract protected function build(): void; private function getNavBar(): FluentHTML { @@ -127,6 +134,14 @@ private function getFooter(): FluentHTML { ); } + public function getResponse(): WebResponse { + return new WebResponse( + $this->getPageOutput(), + [], + $this->responseCode + ); + } + public function getPageOutput(): string { $this->build(); $contents = [ diff --git a/src/Pages/BlogPostPage.php b/src/Pages/BlogPostPage.php index e89a7f2..88daa24 100644 --- a/src/Pages/BlogPostPage.php +++ b/src/Pages/BlogPostPage.php @@ -51,6 +51,7 @@ protected function build(): void { ] ) ); + $this->setResponseCode( 404 ); return; } $this->head->append( diff --git a/src/Pages/Error404Page.php b/src/Pages/Error404Page.php index 7e3ba7c..57c8d04 100644 --- a/src/Pages/Error404Page.php +++ b/src/Pages/Error404Page.php @@ -15,6 +15,7 @@ public function __construct( string $requestedPage ) { FluentHTML::fromTag( 'title' )->addChild( 'Error' ) ); $this->requestedPage = $requestedPage; + $this->setResponseCode( 404 ); } protected function build(): void { diff --git a/src/Pages/Error405Page.php b/src/Pages/Error405Page.php index a5a5de2..e92c391 100644 --- a/src/Pages/Error405Page.php +++ b/src/Pages/Error405Page.php @@ -23,6 +23,7 @@ public function __construct( $this->requestedPage = $requestedPage; $this->requestedMethod = $requestedMethod; $this->allowedMethods = $allowedMethods; + $this->setResponseCode( 405 ); } protected function build(): void { diff --git a/src/Pages/RedirectPage.php b/src/Pages/RedirectPage.php index df3874c..598bd51 100644 --- a/src/Pages/RedirectPage.php +++ b/src/Pages/RedirectPage.php @@ -4,11 +4,11 @@ namespace DanielWebsite\Pages; use DanielWebsite\SitemapEntry; +use DanielWebsite\WebResponse; use FastRoute\RouteCollector; -use LogicException; #[SitemapEntry( 'Resume' )] -class RedirectPage extends BasePage { +class RedirectPage extends AbstractPage { private const KNOWN_REDIRECTS = [ 'Resume' => '/files/Resume.pdf', @@ -17,7 +17,6 @@ class RedirectPage extends BasePage { private string $title; public function __construct( array $params ) { - parent::__construct(); $title = $params['title']; $this->title = $title; } @@ -28,14 +27,13 @@ public static function addRoutes( RouteCollector $r ): void { } } - protected function build(): never { - throw new LogicException( "Should not be called" ); - } - - public function getPageOutput(): string { + public function getResponse(): WebResponse { $target = self::KNOWN_REDIRECTS[ $this->title ]; - header( 'Location: //' . $_SERVER['HTTP_HOST'] . $target ); - return ''; + return new WebResponse( + '', + [ 'Location: //' . $_SERVER['HTTP_HOST'] . $target ], + 302 + ); } } diff --git a/src/Pages/RobotsTxtPage.php b/src/Pages/RobotsTxtPage.php index ca23ae4..2be9141 100644 --- a/src/Pages/RobotsTxtPage.php +++ b/src/Pages/RobotsTxtPage.php @@ -3,19 +3,22 @@ namespace DanielWebsite\Pages; -use LogicException; +use DanielWebsite\WebResponse; /** * Virtual page for serving robots.txt */ -class RobotsTxtPage extends BasePage { +class RobotsTxtPage extends AbstractPage { - protected function build(): never { - throw new LogicException( "Should not be called" ); + public function getResponse(): WebResponse { + return new WebResponse( + $this->getContent(), + [ 'Content-Type: text/plain' ], + 200 + ); } - public function getPageOutput(): string { - header( 'Content-Type: text/plain' ); + private function getContent(): string { if ( getenv( 'DANIEL_WEBSITE_STAGING' ) ) { return "User-agent: *\nDisallow: /\n"; } diff --git a/src/Pages/ToolPage.php b/src/Pages/ToolPage.php index f9efe85..19f2e3f 100644 --- a/src/Pages/ToolPage.php +++ b/src/Pages/ToolPage.php @@ -45,6 +45,7 @@ protected function build(): void { ) ); $this->addIndex(); + $this->setResponseCode( 404 ); return; } $this->contentWrapper->append( diff --git a/src/Router.php b/src/Router.php index a79c962..8ee7570 100644 --- a/src/Router.php +++ b/src/Router.php @@ -3,7 +3,7 @@ namespace DanielWebsite; -use DanielWebsite\Pages\BasePage; +use DanielWebsite\Pages\AbstractPage; use DanielWebsite\Pages\BlogIndexPage; use DanielWebsite\Pages\BlogPostPage; use DanielWebsite\Pages\Error404Page; @@ -26,7 +26,7 @@ class Router { public static function pageForRequest( string $requestMethod, string $requestURI - ): BasePage { + ): AbstractPage { // Remove any query parameters if ( str_contains( $requestURI, '?' ) ) { $pos = strpos( $requestURI, '?' ); diff --git a/src/WebResponse.php b/src/WebResponse.php new file mode 100644 index 0000000..dee1c21 --- /dev/null +++ b/src/WebResponse.php @@ -0,0 +1,30 @@ +headers as $header ) { + header( $header ); + } + http_response_code( $this->responseCode ); + echo $this->content; + } + +} diff --git a/tests/StaticOutputTest.php b/tests/StaticOutputTest.php index a3a9044..9932ec6 100644 --- a/tests/StaticOutputTest.php +++ b/tests/StaticOutputTest.php @@ -43,15 +43,19 @@ class StaticOutputTest extends TestCase { public function testPageOutput( string $method, string $request, - string $fileName + string $fileName, + int $responseCode = 200 ) { $page = Router::pageForRequest( $method, $request ); $filePath = __DIR__ . '/data/' . $fileName; - $output = $page->getPageOutput(); + $response = $page->getResponse(); + $output = $response->content; if ( getenv( 'TESTS_UPDATE_EXPECTED' ) === '1' ) { file_put_contents( $filePath, $output ); } $this->assertStringEqualsFile( $filePath, $output ); + $this->assertSame( [], $response->headers ); + $this->assertSame( $responseCode, $response->responseCode ); } public static function providePages() { @@ -63,10 +67,10 @@ public static function providePages() { yield 'Thesis' => [ 'GET', '/Thesis', 'Thesis.html' ]; yield 'Work' => [ 'GET', '/Work', 'Work.html' ]; yield 'Blog - exists' => [ 'GET', '/Blog/20250409-website-launch', 'blog-launch.html' ]; - yield 'Blog - missing' => [ 'GET', '/Blog/missing', 'blog-missing.html' ]; + yield 'Blog - missing' => [ 'GET', '/Blog/missing', 'blog-missing.html', 404 ]; yield 'Blog - index' => [ 'GET', '/Blog', 'blog-index.html' ]; yield 'Tools - index' => [ 'GET', '/Tools', 'tools-index.html' ]; - yield 'Tools - missing' => [ 'GET', '/Tools/missing', 'tools-missing.html' ]; + yield 'Tools - missing' => [ 'GET', '/Tools/missing', 'tools-missing.html', 404 ]; yield 'Tools - dvorak' => [ 'GET', '/Tools/dvorak', 'tools-dvorak.html' ]; yield 'Blog - pygments highlight' => [ 'GET', @@ -83,10 +87,10 @@ public static function providePages() { yield 'Case insensitive' => [ 'GET', '/oPEnSouRcE', 'OpenSource.html' ]; // Error 404 - yield 'Missing' => [ 'GET', '/Missing', 'Missing.html' ]; + yield 'Missing' => [ 'GET', '/Missing', 'Missing.html', 404 ]; // Error 405 - yield 'POST work page' => [ 'POST', '/Work', 'NoPost.html' ]; + yield 'POST work page' => [ 'POST', '/Work', 'NoPost.html', 405 ]; } public function testRedirect() {