New error handler for API.

This commit is contained in:
2024-10-16 20:05:26 +02:00
parent 1bf738be1c
commit facd5bc9c0
3 changed files with 178 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
<?php
namespace App;
use App\DTO\ApiErrorMessage;
use Symfony\Component\HttpKernel\Exception\HttpException;
class ApiErrorException extends HttpException
{
private ApiErrorMessage $apiErrorMsg;
public function __construct(ApiErrorMessage $apiErrorMsg, $detailsData = null, \Exception $previous = null, array $headers = array(), $code = 0)
{
$this->apiErrorMsg = $apiErrorMsg;
if (isset($detailsData)) {
$this->apiErrorMsg->setDetails($detailsData);
}
$statusCode = $apiErrorMsg->getStatusCode();
$message = $apiErrorMsg->getTitle();
parent::__construct($statusCode, $message, $previous, $headers, $code);
}
public function getApiErrorMsg(): ApiErrorMessage
{
return $this->apiErrorMsg;
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
namespace App\DTO;
use Symfony\Component\HttpFoundation\Response;
class ApiErrorMessage
{
const TYPE_VALIDATION_ERROR = 'validation_error';
const TYPE_INVALID_REQUEST_BODY_FORMAT = 'invalid_body_format';
const TYPE_LOGICAL_ERROR = 'logical_error';
const TYPE_SERVER_ERROR = 'server_error';
const TYPE_INVALID_DATE = 'invalid_date';
private static $titles = array(
self::TYPE_VALIDATION_ERROR => 'There was a validation error',
self::TYPE_INVALID_REQUEST_BODY_FORMAT => 'Invalid JSON format sent',
self::TYPE_LOGICAL_ERROR => 'Invalid request due to a logic error',
self::TYPE_SERVER_ERROR => 'Error on server side',
self::TYPE_INVALID_DATE => 'Invalid date on request',
);
private int $statusCode;
private string $type;
private string $title;
private array $extraData = array();
public function __construct($statusCode, $type = null)
{
if ($type === null) {
// no type? The default is about:blank and the title should
// be the standard status code message
$type = 'about:blank';
$title = isset(Response::$statusTexts[$statusCode]) ?
Response::$statusTexts[$statusCode] : 'Unknown status code :(';
} else {
if (!isset(self::$titles[$type])) {
throw new \InvalidArgumentException('No title for type ' . $type);
}
$title = self::$titles[$type];
}
$this->statusCode = $statusCode;
$this->type = $type;
$this->title = $title;
}
public function getStatusCode(): int
{
return $this->statusCode;
}
public function getTitle(): string
{
return $this->title;
}
public function setExtraData($name, $value)
{
$this->extraData[$name] = $value;
}
public function setDetails($value)
{
$this->extraData['details'] = $value;
}
public function toArray(): array
{
return array_merge(
$this->extraData,
array(
'status' => $this->statusCode,
'type' => $this->type,
'title' => $this->title,
)
);
}
}
@@ -0,0 +1,67 @@
<?php
namespace App\EventSubscriber;
use App\ApiErrorException;
use App\DTO\ApiErrorMessage;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
class ApiExceptionSubscriber implements EventSubscriberInterface
{
public function __construct(
private LoggerInterface $logger,
) {}
public function onKernelException(ExceptionEvent $event): void
{
$e = $event->getThrowable();
if ($e instanceof ApiErrorException) {
$apiErrorMsg = $e->getApiErrorMsg();
} else {
$statusCode = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500;
$apiErrorMsg = new ApiErrorMessage($statusCode);
/*
* If it's an HttpException message (e.g. for 404, 403),
* we'll say as a rule that the exception message is safe
* for the client. Otherwise, it could be some sensitive
* low-level exception, which should *not* be exposed
*/
if ($e instanceof HttpExceptionInterface) {
$apiErrorMsg->setExtraData('detail', $e->getMessage());
}
}
//Send error to logs.
$exceptionError = [
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTrace()
];
$errorContext = array_merge($apiErrorMsg->toArray(), $exceptionError);
$this->logger->error("API ERROR Exception:", $errorContext);
//Send error to client.
$response = new JsonResponse(
$apiErrorMsg->toArray(),
$apiErrorMsg->getStatusCode()
);
$response->headers->set('Content-Type', 'application/problem+json');
$event->setResponse($response);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::EXCEPTION => 'onKernelException',
];
}
}