Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored exception/error handlers. #322

Merged
merged 4 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/Ouzo/Core/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -109,15 +111,15 @@ public function runApplication(): FrontController
private function registerErrorHandlers(): void
{
if (Config::getValue('debug')) {
(new DebugErrorHandler())->register();
(new DebugErrorHandler(new DebugExceptionHandler()))->register();
return;
}

if (!is_null($this->errorHandler)) {
$this->errorHandler->register();
return;
}
(new ErrorHandler())->register();
(new ErrorHandler(new ExceptionHandler()))->register();
}

private function includeRoutes(): void
Expand Down
25 changes: 10 additions & 15 deletions src/Ouzo/Core/ExceptionHandling/DebugErrorHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
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';
piotrooo marked this conversation as resolved.
Show resolved Hide resolved
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');
}
}
Loading