diff --git a/src/Ouzo/Core/Bootstrap.php b/src/Ouzo/Core/Bootstrap.php index b107c725..f0366a83 100644 --- a/src/Ouzo/Core/Bootstrap.php +++ b/src/Ouzo/Core/Bootstrap.php @@ -10,7 +10,9 @@ use InvalidArgumentException; use Ouzo\Config\ConfigRepository; use Ouzo\ExceptionHandling\DebugErrorHandler; +use Ouzo\ExceptionHandling\DebugExceptionHandler; use Ouzo\ExceptionHandling\ErrorHandler; +use Ouzo\ExceptionHandling\ExceptionHandler; use Ouzo\Injection\Injector; use Ouzo\Injection\InjectorConfig; use Ouzo\Injection\Scope; @@ -109,7 +111,7 @@ public function runApplication(): FrontController private function registerErrorHandlers(): void { if (Config::getValue('debug')) { - (new DebugErrorHandler())->register(); + (new DebugErrorHandler(new DebugExceptionHandler()))->register(); return; } @@ -117,7 +119,7 @@ private function registerErrorHandlers(): void $this->errorHandler->register(); return; } - (new ErrorHandler())->register(); + (new ErrorHandler(new ExceptionHandler()))->register(); } private function includeRoutes(): void diff --git a/src/Ouzo/Core/ExceptionHandling/DebugErrorHandler.php b/src/Ouzo/Core/ExceptionHandling/DebugErrorHandler.php index 3389bb80..99a046fb 100644 --- a/src/Ouzo/Core/ExceptionHandling/DebugErrorHandler.php +++ b/src/Ouzo/Core/ExceptionHandling/DebugErrorHandler.php @@ -11,27 +11,22 @@ class DebugErrorHandler extends ErrorHandler { - protected static function getRun(): Run + public function handleError(int $errorNumber, string $errorString, string $errorFile, int $errorLine): void { - error_reporting(E_ALL); - $run = new Run(); - $run->pushHandler(new PrettyPageHandler()); - $run->pushHandler(new DebugErrorLogHandler()); - return $run; + $this->createWhoops()->handleError($errorNumber, $errorString, $errorFile, $errorLine); } - protected static function getExceptionHandler(): ExceptionHandler + public function handleShutdown(): void { - return new DebugExceptionHandler(); + $this->createWhoops()->handleShutdown(); } - public static function errorHandler(int $errorNumber, string $errorString, string $errorFile, int $errorLine): void + private function createWhoops(): Run { - self::getRun()->handleError($errorNumber, $errorString, $errorFile, $errorLine); - } - - public static function shutdownHandler(): void - { - self::getRun()->handleShutdown(); + error_reporting(E_ALL); + $run = new Run(); + $run->pushHandler(new PrettyPageHandler()); + $run->pushHandler(new DebugErrorLogHandler()); + return $run; } } diff --git a/src/Ouzo/Core/ExceptionHandling/DebugExceptionHandler.php b/src/Ouzo/Core/ExceptionHandling/DebugExceptionHandler.php index cd629b78..b8bbdf3a 100644 --- a/src/Ouzo/Core/ExceptionHandling/DebugExceptionHandler.php +++ b/src/Ouzo/Core/ExceptionHandling/DebugExceptionHandler.php @@ -6,6 +6,7 @@ namespace Ouzo\ExceptionHandling; +use Ouzo\Http\MediaType; use Ouzo\Response\ResponseTypeResolve; use Ouzo\Uri; use Whoops\Handler\PrettyPageHandler; @@ -13,9 +14,9 @@ class DebugExceptionHandler extends ExceptionHandler { - public function runDefaultHandler($exception) + public function runDefaultHandler($exception): void { - if ($this->needPrettyHandler()) { + if ($this->isPrettyHandlerNeeded()) { $run = new Run(); $run->pushHandler(new PrettyPageHandler()); $run->pushHandler(new DebugErrorLogHandler()); @@ -25,9 +26,9 @@ public function runDefaultHandler($exception) } } - private function needPrettyHandler(): bool + private function isPrettyHandlerNeeded(): bool { - $isHtmlResponse = ResponseTypeResolve::resolve() == "text/html"; + $isHtmlResponse = ResponseTypeResolve::resolve() === MediaType::TEXT_HTML; return $isHtmlResponse && !Uri::isAjax(); } } diff --git a/src/Ouzo/Core/ExceptionHandling/ErrorHandler.php b/src/Ouzo/Core/ExceptionHandling/ErrorHandler.php index acc6c39a..307d5345 100644 --- a/src/Ouzo/Core/ExceptionHandling/ErrorHandler.php +++ b/src/Ouzo/Core/ExceptionHandling/ErrorHandler.php @@ -11,28 +11,32 @@ class ErrorHandler { + public function __construct(private readonly ExceptionHandler $exceptionHandler) + { + } + public function register(): void { - set_exception_handler(fn(Throwable $exception) => static::exceptionHandler($exception)); - set_error_handler(fn(...$args) => static::errorHandler(...$args), E_ALL & ~E_DEPRECATED & ~E_STRICT); - register_shutdown_function(fn() => static::shutdownHandler()); + set_exception_handler(fn(Throwable $exception) => $this->handleException($exception)); + set_error_handler(fn(...$args) => $this->handleError(...$args), E_ALL & ~E_DEPRECATED & ~E_STRICT); + register_shutdown_function(fn() => $this->handleShutdown()); } - public static function exceptionHandler(Throwable $exception): void + public function handleException(Throwable $exception): void { - static::getExceptionHandler()->handleException($exception); + $this->exceptionHandler->handleException($exception); } - public static function errorHandler(int $errorNumber, string $errorString, string $errorFile, int $errorLine): void + public function handleError(int $errorNumber, string $errorString, string $errorFile, int $errorLine): void { - if (self::stopsExecution($errorNumber)) { - self::exceptionHandler(new ErrorException($errorString, $errorNumber, $errorNumber, $errorFile, $errorLine)); + if ($this->stopsExecution($errorNumber)) { + $this->handleException(new ErrorException($errorString, $errorNumber, $errorNumber, $errorFile, $errorLine)); } else { throw new ErrorException($errorString, $errorNumber, $errorNumber, $errorFile, $errorLine); } } - public static function stopsExecution($errno): bool + public function stopsExecution($errno): bool { return match ($errno) { E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_USER_ERROR => true, @@ -40,19 +44,14 @@ public static function stopsExecution($errno): bool }; } - protected static function getExceptionHandler(): ExceptionHandler - { - return new ExceptionHandler(); - } - - public static function shutdownHandler(): void + public function handleShutdown(): void { $error = error_get_last(); - if (!ExceptionHandler::lastErrorHandled() && $error && $error['type'] & (E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_RECOVERABLE_ERROR)) { + if (!$this->exceptionHandler->lastErrorHandled() && $error && $error['type'] & (E_ERROR | E_USER_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_RECOVERABLE_ERROR)) { $stackTrace = new StackTrace($error['file'], $error['line']); $exceptionData = new OuzoExceptionData(500, [new Error(0, $error['message'])], $stackTrace, [], null, $error['type']); - static::getExceptionHandler()->handleExceptionData($exceptionData); + $this->exceptionHandler->handleExceptionData($exceptionData); } } } diff --git a/src/Ouzo/Core/ExceptionHandling/ErrorRenderer.php b/src/Ouzo/Core/ExceptionHandling/ErrorRenderer.php index 635983ba..c9795d1f 100644 --- a/src/Ouzo/Core/ExceptionHandling/ErrorRenderer.php +++ b/src/Ouzo/Core/ExceptionHandling/ErrorRenderer.php @@ -13,26 +13,23 @@ class ErrorRenderer implements Renderer { public function render(OuzoExceptionData $exceptionData, ?string $viewName): void { - /** @noinspection PhpUnusedLocalVariableInspection */ $errorMessage = $exceptionData->getMessage(); - /** @noinspection PhpUnusedLocalVariableInspection */ $errorTrace = $exceptionData->getStackTrace()->getTraceAsString(); $this->clearOutputBuffers(); header($exceptionData->getHeader()); $responseType = ResponseTypeResolve::resolve(); - header('Content-type: ' . $responseType); + header("Content-type: {$responseType}"); $additionalHeaders = $exceptionData->getAdditionalHeaders(); array_walk($additionalHeaders, function ($header) { header($header); }); - /** @noinspection PhpIncludeInspection */ require(ViewPathResolver::resolveViewPath($viewName, $responseType)); } - private function clearOutputBuffers() + private function clearOutputBuffers(): void { while (ob_get_level()) { if (!ob_end_clean()) { diff --git a/src/Ouzo/Core/ExceptionHandling/ExceptionHandler.php b/src/Ouzo/Core/ExceptionHandling/ExceptionHandler.php index 55d3262d..b267f68e 100644 --- a/src/Ouzo/Core/ExceptionHandling/ExceptionHandler.php +++ b/src/Ouzo/Core/ExceptionHandling/ExceptionHandler.php @@ -13,74 +13,75 @@ class ExceptionHandler { - private static bool $errorHandled = false; - private static bool $isCli = false; - public static ?Renderer $errorRenderer = null; + private bool $errorHandled = false; + private bool $isCli; + private Renderer $errorRenderer; - public static function setupErrorRenderer() + public function __construct(?Renderer $errorRenderer = null) { global $argv; - self::$isCli = isset($argv[0]); - self::$errorRenderer = self::$isCli ? new CliErrorRenderer() : new ErrorRenderer(); + $this->isCli = isset($argv[0]); + $this->errorRenderer = is_null($errorRenderer) ? ($this->isCli ? new CliErrorRenderer() : new ErrorRenderer()) : $errorRenderer; } - public function handleException($exception) + public function handleException($exception): void { if (!$this->runOuzoExceptionHandler($exception)) { $this->runDefaultHandler($exception); } } - protected function runOuzoExceptionHandler($exception) + protected function runOuzoExceptionHandler($exception): bool { if ($exception instanceof UserException) { $this->renderUserError(OuzoExceptionData::forException(500, $exception)); return true; - } elseif ($exception instanceof RouterException) { + } + if ($exception instanceof RouterException) { $this->handleError(OuzoExceptionData::forException(404, $exception)); return true; - } elseif ($exception instanceof OuzoException) { + } + if ($exception instanceof OuzoException) { $this->handleError($exception->asExceptionData()); return true; } return false; } - protected function runDefaultHandler($exception) + protected function runDefaultHandler($exception): void { $this->handleError(OuzoExceptionData::forException(500, $exception)); } - public function handleExceptionData(OuzoExceptionData $exceptionData) + public function handleExceptionData(OuzoExceptionData $exceptionData): void { $this->handleError($exceptionData); } - public static function lastErrorHandled() + public function lastErrorHandled(): bool { - return self::$errorHandled; + return $this->errorHandled; } - protected function handleError($exception) + protected function handleError($exception): void { $this->renderError($exception); } - private function renderUserError($exception) + private function renderUserError($exception): void { - if (!self::$isCli) { - header("Contains-Error-Message: User"); + if (!$this->isCli) { + header('Contains-Error-Message: User'); } $this->renderError($exception, 'user_exception'); } - protected function renderError(OuzoExceptionData $exceptionData, $viewName = 'exception') + protected function renderError(OuzoExceptionData $exceptionData, $viewName = 'exception'): void { try { ExceptionLogger::newInstance($exceptionData)->log(); - $renderer = self::$errorRenderer ?: new ErrorRenderer(); - $renderer->render($exceptionData, $viewName); - self::$errorHandled = true; + $this->errorRenderer->render($exceptionData, $viewName); + $this->errorHandled = true; } catch (Exception $e) { echo "Framework critical error. Exception thrown in exception handler.
\n"; ExceptionLogger::forException($e)->log(); @@ -92,5 +93,3 @@ protected function renderError(OuzoExceptionData $exceptionData, $viewName = 'ex } } } - -ExceptionHandler::setupErrorRenderer(); \ No newline at end of file diff --git a/test/src/Ouzo/Core/ExceptionHandling/ErrorHandlerTest.php b/test/src/Ouzo/Core/ExceptionHandling/ErrorHandlerTest.php index c4e4cb0a..845e012a 100644 --- a/test/src/Ouzo/Core/ExceptionHandling/ErrorHandlerTest.php +++ b/test/src/Ouzo/Core/ExceptionHandling/ErrorHandlerTest.php @@ -8,6 +8,7 @@ use Ouzo\PageNotFoundException; use Ouzo\Tests\Mock\Mock; +use Ouzo\Tests\Mock\MockInterface; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -18,12 +19,14 @@ public function shouldRender404OnRouterException() { //given $pageNotFoundException = new PageNotFoundException(); - ExceptionHandler::$errorRenderer = Mock::mock(ErrorRenderer::class); + /** @var Renderer|MockInterface $renderer */ + $renderer = Mock::create(ErrorRenderer::class); + $handler = new ErrorHandler(new ExceptionHandler($renderer)); //when - ErrorHandler::exceptionHandler($pageNotFoundException); + $handler->handleException($pageNotFoundException); //then - Mock::verify(ExceptionHandler::$errorRenderer)->render(Mock::any(), "exception"); + Mock::verify($renderer)->render(Mock::any(), 'exception'); } } diff --git a/test/src/Ouzo/Core/ExceptionHandling/ExceptionHandlerTest.php b/test/src/Ouzo/Core/ExceptionHandling/ExceptionHandlerTest.php index 89ce1437..3e88919b 100644 --- a/test/src/Ouzo/Core/ExceptionHandling/ExceptionHandlerTest.php +++ b/test/src/Ouzo/Core/ExceptionHandling/ExceptionHandlerTest.php @@ -6,8 +6,10 @@ namespace Ouzo\ExceptionHandling; +use Exception; use Ouzo\Tests\CatchException; use Ouzo\Tests\Mock\Mock; +use Ouzo\Tests\Mock\MockInterface; use PHPUnit\Framework\Attributes\Test; use PHPUnit\Framework\TestCase; @@ -17,15 +19,16 @@ class ExceptionHandlerTest extends TestCase public function shouldHandleException() { //given - $exception = new \Exception("Some exception"); - ExceptionHandler::$errorRenderer = Mock::mock(ErrorRenderer::class); - $handler = new ExceptionHandler(); + $exception = new Exception('Some exception'); + /** @var Renderer|MockInterface $renderer */ + $renderer = Mock::create(ErrorRenderer::class); + $handler = new ExceptionHandler($renderer); //when CatchException::when($handler)->handleException($exception); //then CatchException::assertThat()->notCaught(); - Mock::verify(ExceptionHandler::$errorRenderer)->render(OuzoExceptionData::forException(500, $exception), "exception"); + Mock::verify($renderer)->render(OuzoExceptionData::forException(500, $exception), 'exception'); } }