forked from Somnia/SomniaRooms
New error handler for API.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user