Skip to content

Commit

Permalink
Refactored exception/error handlers.
Browse files Browse the repository at this point in the history
  • Loading branch information
bbankowski committed Dec 11, 2023
1 parent be9a039 commit 113b333
Show file tree
Hide file tree
Showing 7 changed files with 58 additions and 57 deletions.
4 changes: 2 additions & 2 deletions src/Ouzo/Core/ExceptionHandling/DebugErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ protected static function getExceptionHandler(): ExceptionHandler
return new DebugExceptionHandler();
}

public static function errorHandler(int $errorNumber, string $errorString, string $errorFile, int $errorLine): void
public static function handleError(int $errorNumber, string $errorString, string $errorFile, int $errorLine): void
{
self::getRun()->handleError($errorNumber, $errorString, $errorFile, $errorLine);
}

public static function shutdownHandler(): void
public static function handleShutdown(): void
{
self::getRun()->handleShutdown();
}
Expand Down
4 changes: 2 additions & 2 deletions src/Ouzo/Core/ExceptionHandling/DebugExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

class DebugExceptionHandler extends ExceptionHandler
{
public function runDefaultHandler($exception)
public function runDefaultHandler($exception): void
{
if ($this->needPrettyHandler()) {
$run = new Run();
Expand All @@ -27,7 +27,7 @@ public function runDefaultHandler($exception)

private function needPrettyHandler(): bool
{
$isHtmlResponse = ResponseTypeResolve::resolve() == "text/html";
$isHtmlResponse = ResponseTypeResolve::resolve() === 'text/html';
return $isHtmlResponse && !Uri::isAjax();
}
}
33 changes: 16 additions & 17 deletions src/Ouzo/Core/ExceptionHandling/ErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,48 +11,47 @@

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,
default => false
};
}

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);
}
}
}
7 changes: 2 additions & 5 deletions src/Ouzo/Core/ExceptionHandling/ErrorRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
47 changes: 23 additions & 24 deletions src/Ouzo/Core/ExceptionHandling/ExceptionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br>\n";
ExceptionLogger::forException($e)->log();
Expand All @@ -92,5 +93,3 @@ protected function renderError(OuzoExceptionData $exceptionData, $viewName = 'ex
}
}
}

ExceptionHandler::setupErrorRenderer();
9 changes: 6 additions & 3 deletions test/src/Ouzo/Core/ExceptionHandling/ErrorHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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');
}
}
11 changes: 7 additions & 4 deletions test/src/Ouzo/Core/ExceptionHandling/ExceptionHandlerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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');
}
}

0 comments on commit 113b333

Please sign in to comment.