From 61ee74d0ce0680eee86af02dac08917470cc3c05 Mon Sep 17 00:00:00 2001 From: katarn Date: Thu, 20 Apr 2023 19:26:05 +0200 Subject: [PATCH 1/2] Handle 404 erros with handler (also can be overwritten) --- .../Controllers/NotFound404Controller.php | 13 ---------- .../Exceptions/NotFound404Exception.php | 15 ++++++++++++ src/Router/Handlers.php | 18 +++++++++++++- .../Handlers/NotFound404ExceptionHandler.php | 17 +++++++++++++ src/Router/Router.php | 7 +++--- tests/Feature/Router/ErrorHandlingTest.php | 24 ++++++++++++++++++- tests/Feature/header.php | 24 +++++++++++++++++-- 7 files changed, 98 insertions(+), 20 deletions(-) delete mode 100644 src/Router/Controllers/NotFound404Controller.php create mode 100644 src/Router/Exceptions/NotFound404Exception.php create mode 100644 src/Router/Handlers/NotFound404ExceptionHandler.php diff --git a/src/Router/Controllers/NotFound404Controller.php b/src/Router/Controllers/NotFound404Controller.php deleted file mode 100644 index 74a1a43..0000000 --- a/src/Router/Controllers/NotFound404Controller.php +++ /dev/null @@ -1,13 +0,0 @@ - */ private array $handlers = []; + public function __construct() + { + $this->addBuiltInHandlers(); + } /** - * @param class-string $exception + * @param class-string $exception */ public function handle(string $exception, callable $handler): self { @@ -25,4 +33,12 @@ public function getAllHandlers(): array { return $this->handlers; } + + private function addBuiltInHandlers(): void + { + $this->handle( + NotFound404Exception::class, + static fn (NotFound404Exception $exception) => (new NotFound404ExceptionHandler())->__invoke($exception), + ); + } } diff --git a/src/Router/Handlers/NotFound404ExceptionHandler.php b/src/Router/Handlers/NotFound404ExceptionHandler.php new file mode 100644 index 0000000..6e195cc --- /dev/null +++ b/src/Router/Handlers/NotFound404ExceptionHandler.php @@ -0,0 +1,17 @@ +run($bindings); + echo self::findRoute($routes) + ->run($bindings); } catch (Exception $exception) { echo self::handleException($handlers, $exception); } @@ -48,7 +49,7 @@ private static function findRoute(Routes $routes): Route } } - return new Route('', '/', NotFound404Controller::class); + throw new NotFound404Exception(); } private static function handleException(Handlers $handlers, Exception $exception): string diff --git a/tests/Feature/Router/ErrorHandlingTest.php b/tests/Feature/Router/ErrorHandlingTest.php index cd65005..66566aa 100644 --- a/tests/Feature/Router/ErrorHandlingTest.php +++ b/tests/Feature/Router/ErrorHandlingTest.php @@ -5,6 +5,7 @@ namespace GacelaTest\Feature\Router; use Gacela\Router\Entities\Request; +use Gacela\Router\Exceptions\NotFound404Exception; use Gacela\Router\Handlers; use Gacela\Router\Router; use Gacela\Router\Routes; @@ -101,7 +102,7 @@ public function test_respond_500_status_when_unhandled_exception(): void ], $this->headers()); } - public function test_handle_handled_exception(): void + public function test_handle_handled_exception_with_anonymous_function(): void { $_SERVER['REQUEST_URI'] = 'https://example.org/expected/uri'; $_SERVER['REQUEST_METHOD'] = Request::METHOD_GET; @@ -116,7 +117,28 @@ public function test_handle_handled_exception(): void }); $this->expectOutputString('Handled!'); + self::assertSame([ + [ + 'header' => 'HTTP/1.1 418 I\'m a teapot', + 'replace' => true, + 'response_code' => 0, + ], + ], $this->headers()); + } + public function test_custom_404_handler(): void + { + $_SERVER['REQUEST_URI'] = 'https://example.org/expected/uri'; + $_SERVER['REQUEST_METHOD'] = Request::METHOD_GET; + + Router::configure(static function (Routes $routes, Handlers $handlers): void { + $handlers->handle(NotFound404Exception::class, static function (): string { + \Gacela\Router\header('HTTP/1.1 418 I\'m a teapot'); + return 'Handled!'; + }); + }); + + $this->expectOutputString('Handled!'); self::assertSame([ [ 'header' => 'HTTP/1.1 418 I\'m a teapot', diff --git a/tests/Feature/header.php b/tests/Feature/header.php index 58506b3..2de7028 100644 --- a/tests/Feature/header.php +++ b/tests/Feature/header.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Gacela\Router\Controllers { +namespace Gacela\Router { function header(string $header, bool $replace = true, int $responseCode = 0): void { /** @var list | null $testHeaders */ @@ -22,7 +22,27 @@ function header(string $header, bool $replace = true, int $responseCode = 0): vo // TODO: Find a better way to mock the head function in different namespaces -namespace Gacela\Router { +namespace Gacela\Router\Handlers { + function header(string $header, bool $replace = true, int $responseCode = 0): void + { + /** @var list | null $testHeaders */ + global $testHeaders; + + if (!\is_array($testHeaders)) { + $testHeaders = []; + } + + $testHeaders[] = [ + 'header' => $header, + 'replace' => $replace, + 'response_code' => $responseCode, + ]; + } +} + +// TODO: Find a better way to mock the head function in different namespaces + +namespace Gacela\Router\Controllers { function header(string $header, bool $replace = true, int $responseCode = 0): void { /** @var list | null $testHeaders */ From fe43af1f3e387931e2a78de1736d25a80fb18097 Mon Sep 17 00:00:00 2001 From: katarn Date: Thu, 20 Apr 2023 19:38:29 +0200 Subject: [PATCH 2/2] Handle 500 errors with handler (also can be overwritten) --- src/Router/Handlers.php | 5 ++++ .../Handlers/FallbackExceptionHandler.php | 17 ++++++++++++ src/Router/Router.php | 9 +++---- tests/Feature/Router/ErrorHandlingTest.php | 27 ++++++++++++++++++- 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 src/Router/Handlers/FallbackExceptionHandler.php diff --git a/src/Router/Handlers.php b/src/Router/Handlers.php index 0703cf4..35cc177 100644 --- a/src/Router/Handlers.php +++ b/src/Router/Handlers.php @@ -6,6 +6,7 @@ use Exception; use Gacela\Router\Exceptions\NotFound404Exception; +use Gacela\Router\Handlers\FallbackExceptionHandler; use Gacela\Router\Handlers\NotFound404ExceptionHandler; final class Handlers @@ -40,5 +41,9 @@ private function addBuiltInHandlers(): void NotFound404Exception::class, static fn (NotFound404Exception $exception) => (new NotFound404ExceptionHandler())->__invoke($exception), ); + $this->handle( + Exception::class, + static fn (Exception $exception) => (new FallbackExceptionHandler())->__invoke($exception), + ); } } diff --git a/src/Router/Handlers/FallbackExceptionHandler.php b/src/Router/Handlers/FallbackExceptionHandler.php new file mode 100644 index 0000000..ea3127e --- /dev/null +++ b/src/Router/Handlers/FallbackExceptionHandler.php @@ -0,0 +1,17 @@ +run($bindings); } catch (Exception $exception) { - echo self::handleException($handlers, $exception); + echo (string) self::findHandler($handlers, $exception)($exception); } } @@ -52,15 +52,14 @@ private static function findRoute(Routes $routes): Route throw new NotFound404Exception(); } - private static function handleException(Handlers $handlers, Exception $exception): string + private static function findHandler(Handlers $handlers, Exception $exception): callable { $handler = $handlers->getAllHandlers()[get_class($exception)] ?? null; if ($handler === null) { - header('HTTP/1.1 500 Internal Server Error'); - return ''; + return $handlers->getAllHandlers()[Exception::class]; } - return $handler($exception); + return $handler; } } diff --git a/tests/Feature/Router/ErrorHandlingTest.php b/tests/Feature/Router/ErrorHandlingTest.php index 66566aa..e181ff8 100644 --- a/tests/Feature/Router/ErrorHandlingTest.php +++ b/tests/Feature/Router/ErrorHandlingTest.php @@ -4,6 +4,7 @@ namespace GacelaTest\Feature\Router; +use Exception; use Gacela\Router\Entities\Request; use Gacela\Router\Exceptions\NotFound404Exception; use Gacela\Router\Handlers; @@ -131,7 +132,7 @@ public function test_custom_404_handler(): void $_SERVER['REQUEST_URI'] = 'https://example.org/expected/uri'; $_SERVER['REQUEST_METHOD'] = Request::METHOD_GET; - Router::configure(static function (Routes $routes, Handlers $handlers): void { + Router::configure(static function (Handlers $handlers): void { $handlers->handle(NotFound404Exception::class, static function (): string { \Gacela\Router\header('HTTP/1.1 418 I\'m a teapot'); return 'Handled!'; @@ -147,4 +148,28 @@ public function test_custom_404_handler(): void ], ], $this->headers()); } + + public function test_custom_fallback_handler(): void + { + $_SERVER['REQUEST_URI'] = 'https://example.org/expected/uri'; + $_SERVER['REQUEST_METHOD'] = Request::METHOD_GET; + + Router::configure(static function (Handlers $handlers, Routes $routes): void { + $routes->get('expected/uri', FakeControllerWithUnhandledException::class); + + $handlers->handle(Exception::class, static function (): string { + \Gacela\Router\header('HTTP/1.1 418 I\'m a teapot'); + return 'Handled!'; + }); + }); + + $this->expectOutputString('Handled!'); + self::assertSame([ + [ + 'header' => 'HTTP/1.1 418 I\'m a teapot', + 'replace' => true, + 'response_code' => 0, + ], + ], $this->headers()); + } }