77 lines
2.3 KiB
PHP
77 lines
2.3 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace App\Http;
|
||
|
|
|
||
|
|
use App\Exception\ApiException;
|
||
|
|
use Psr\Http\Message\ResponseFactoryInterface;
|
||
|
|
use Psr\Http\Message\ResponseInterface as Response;
|
||
|
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||
|
|
use Slim\Exception\HttpMethodNotAllowedException;
|
||
|
|
use Slim\Exception\HttpNotFoundException;
|
||
|
|
use Throwable;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Renders every uncaught error as a consistent JSON envelope:
|
||
|
|
*
|
||
|
|
* { "error": { "message": string, "details"?: object } }
|
||
|
|
*/
|
||
|
|
final class JsonErrorHandler
|
||
|
|
{
|
||
|
|
public function __construct(
|
||
|
|
private readonly ResponseFactoryInterface $responseFactory,
|
||
|
|
private readonly bool $displayErrorDetails,
|
||
|
|
) {
|
||
|
|
}
|
||
|
|
|
||
|
|
public function __invoke(
|
||
|
|
Request $request,
|
||
|
|
Throwable $exception,
|
||
|
|
bool $displayErrorDetails,
|
||
|
|
bool $logErrors,
|
||
|
|
bool $logErrorDetails,
|
||
|
|
): Response {
|
||
|
|
[$status, $payload] = $this->describe($exception);
|
||
|
|
|
||
|
|
$response = $this->responseFactory->createResponse($status);
|
||
|
|
$response->getBody()->write(
|
||
|
|
(string) json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
|
||
|
|
);
|
||
|
|
|
||
|
|
return $response->withHeader('Content-Type', 'application/json');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @return array{0: int, 1: array<string, mixed>}
|
||
|
|
*/
|
||
|
|
private function describe(Throwable $exception): array
|
||
|
|
{
|
||
|
|
if ($exception instanceof ApiException) {
|
||
|
|
$error = ['message' => $exception->getMessage()];
|
||
|
|
if ($exception->getDetails() !== []) {
|
||
|
|
$error['details'] = $exception->getDetails();
|
||
|
|
}
|
||
|
|
|
||
|
|
return [$exception->getStatusCode(), ['error' => $error]];
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($exception instanceof HttpNotFoundException) {
|
||
|
|
return [404, ['error' => ['message' => 'The requested resource was not found.']]];
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($exception instanceof HttpMethodNotAllowedException) {
|
||
|
|
return [405, ['error' => ['message' => 'Method not allowed for this resource.']]];
|
||
|
|
}
|
||
|
|
|
||
|
|
$error = ['message' => 'An unexpected error occurred.'];
|
||
|
|
if ($this->displayErrorDetails) {
|
||
|
|
$error['message'] = $exception->getMessage();
|
||
|
|
$error['exception'] = $exception::class;
|
||
|
|
$error['file'] = $exception->getFile() . ':' . $exception->getLine();
|
||
|
|
}
|
||
|
|
|
||
|
|
return [500, ['error' => $error]];
|
||
|
|
}
|
||
|
|
}
|