Skip to content

Commit

Permalink
Replace ErrorMiddleware with ExceptionMiddleware
Browse files Browse the repository at this point in the history
  • Loading branch information
odan committed Dec 3, 2023
1 parent af617eb commit 369b4d3
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 28 deletions.
33 changes: 12 additions & 21 deletions config/container.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use App\Handler\DefaultErrorHandler;
use App\Middleware\ExceptionMiddleware;
use App\Renderer\JsonRenderer;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
use Monolog\Logger;
Expand All @@ -16,7 +17,6 @@
use Slim\App;
use Slim\Factory\AppFactory;
use Slim\Interfaces\RouteParserInterface;
use Slim\Middleware\ErrorMiddleware;

return [
// Application settings
Expand Down Expand Up @@ -66,34 +66,25 @@

LoggerInterface::class => function (ContainerInterface $container) {
$settings = $container->get('settings')['logger'];

$logger = new Logger('app');

if (isset($settings['path'])) {
$filename = sprintf('%s/app.log', $settings['path']);
$level = $settings['level'];
$rotatingFileHandler = new RotatingFileHandler($filename, 0, $level, true, 0777);
$rotatingFileHandler->setFormatter(new LineFormatter(null, null, false, true));
$logger->pushHandler($rotatingFileHandler);
}
$filename = sprintf('%s/app.log', $settings['path']);
$level = $settings['level'];
$rotatingFileHandler = new RotatingFileHandler($filename, 0, $level, true, 0777);
$rotatingFileHandler->setFormatter(new LineFormatter(null, null, false, true));
$logger->pushHandler($rotatingFileHandler);

return $logger;
},

ErrorMiddleware::class => function (ContainerInterface $container) {
ExceptionMiddleware::class => function (ContainerInterface $container) {
$settings = $container->get('settings')['error'];
$app = $container->get(App::class);

$errorMiddleware = new ErrorMiddleware(
$app->getCallableResolver(),
$app->getResponseFactory(),
return new ExceptionMiddleware(
$container->get(ResponseFactoryInterface::class),
$container->get(JsonRenderer::class),
$container->get(LoggerInterface::class),
(bool)$settings['display_error_details'],
(bool)$settings['log_errors'],
(bool)$settings['log_error_details'],
);

$errorMiddleware->setDefaultErrorHandler($container->get(DefaultErrorHandler::class));

return $errorMiddleware;
},
];
4 changes: 0 additions & 4 deletions config/defaults.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@
$settings['error'] = [
// Should be set to false for the production environment
'display_error_details' => false,
// Should be set to false for the test environment
'log_errors' => true,
// Display error details in error log
'log_error_details' => true,
];

// Logger settings
Expand Down
1 change: 0 additions & 1 deletion config/local.test.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

return function (array $settings): array {
$settings['error']['display_error_details'] = true;
$settings['error']['log_errors'] = true;

// Database
$settings['db']['database'] = 'slim_skeleton_test';
Expand Down
4 changes: 2 additions & 2 deletions config/middleware.php
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<?php

use App\Middleware\ExceptionMiddleware;
use Selective\BasePath\BasePathMiddleware;
use Slim\App;
use Slim\Middleware\ErrorMiddleware;

return function (App $app) {
$app->addBodyParsingMiddleware();
$app->addRoutingMiddleware();
$app->add(BasePathMiddleware::class);
$app->add(ErrorMiddleware::class);
$app->add(ExceptionMiddleware::class);
};
119 changes: 119 additions & 0 deletions src/Middleware/ExceptionMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
<?php

namespace App\Middleware;

use App\Renderer\JsonRenderer;
use DomainException;
use Fig\Http\Message\StatusCodeInterface;
use InvalidArgumentException;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Psr\Log\LoggerInterface;
use Slim\Exception\HttpException;
use Throwable;

final class ExceptionMiddleware implements MiddlewareInterface
{
private ResponseFactoryInterface $responseFactory;
private JsonRenderer $renderer;
private ?LoggerInterface $logger;
private bool $displayErrorDetails;

public function __construct(
ResponseFactoryInterface $responseFactory,
JsonRenderer $jsonRenderer,
LoggerInterface $logger = null,
bool $displayErrorDetails = false,
) {
$this->responseFactory = $responseFactory;
$this->renderer = $jsonRenderer;
$this->displayErrorDetails = $displayErrorDetails;
$this->logger = $logger;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
try {
return $handler->handle($request);
} catch (Throwable $exception) {
return $this->render($exception, $request);
}
}

private function render(
Throwable $exception,
ServerRequestInterface $request,
): ResponseInterface {
$httpStatusCode = $this->getHttpStatusCode($exception);
$response = $this->responseFactory->createResponse($httpStatusCode);

// Content negotiation
if (str_contains($request->getHeaderLine('Accept'), 'application/json')) {
$response = $response->withAddedHeader('Content-Type', 'application/json');

// JSON
return $this->renderJson($exception, $response);
}

// HTML
return $this->renderHtml($response, $exception);
}

public function renderJson(Throwable $exception, ResponseInterface $response): ResponseInterface
{
$data = [
'error' => [
'message' => $exception->getMessage(),
],
];

return $this->renderer->json($response, $data);
}

public function renderHtml(ResponseInterface $response, Throwable $exception): ResponseInterface
{
$response = $response->withAddedHeader('Content-Type', 'text/html');

$message = sprintf(
"\n<br>Error %s (%s)\n<br>Message: %s\n<br>",
$this->html($response->getStatusCode()),
$this->html($response->getReasonPhrase()),
$this->html($exception->getMessage()),
);

if ($this->displayErrorDetails) {
$message .= sprintf(
'File: %s, Line: %s',
$this->html($exception->getFile()),
$this->html($exception->getLine())
);
}

$response->getBody()->write($message);

return $response;
}

private function getHttpStatusCode(Throwable $exception): int
{
$statusCode = StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR;

if ($exception instanceof HttpException) {
$statusCode = $exception->getCode();
}

if ($exception instanceof DomainException || $exception instanceof InvalidArgumentException) {
$statusCode = StatusCodeInterface::STATUS_BAD_REQUEST;
}

return $statusCode;
}

private function html(string $text): string
{
return htmlspecialchars($text, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
}

0 comments on commit 369b4d3

Please sign in to comment.