<?php
namespace App\Infrastructure\EventListener;
use App\Database\Domain\Entity\Log;
use App\Database\Domain\Exception\ModelValidationException;
use App\Infrastructure\Logs\DbLogger;
use Doctrine\ORM\ORMException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Messenger\Exception\HandlerFailedException;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Throwable;
class ExceptionListener
{
private RouterInterface $router;
private SessionInterface $session;
private DbLogger $logger;
public function __construct(
RouterInterface $router,
SessionInterface $session,
DbLogger $logger
) {
$this->router = $router;
$this->session = $session;
$this->logger = $logger;
}
public function onKernelException(ExceptionEvent $event): void
{
// Disabling listener in dev env (but not in ajax requests) to get detailed error messages
if ($_ENV['APP_ENV'] === 'dev' && !$this->isApiException($event)) return;
$this->isApiException($event) ?
$this->handleApiException($event) : $this->handleException($event);
}
private function handleApiException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
if ($exception instanceof HandlerFailedException) {
$exception = $exception->getPrevious();
}
try {
$this->logger->error($exception->getMessage(), Log::SOURCE_GLOBAL, ['trace' => $exception->getTraceAsString()]);
} catch (Throwable $e) {
//
}
$response = new JsonResponse(['message' => $exception->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
if ($exception instanceof NotFoundHttpException || $exception instanceof MethodNotAllowedHttpException) {
$response = new JsonResponse(['message' => $exception->getMessage()], Response::HTTP_NOT_FOUND);
}
if ($exception instanceof BadRequestHttpException) {
$response = new JsonResponse(['message' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
}
if ($exception instanceof AccessDeniedHttpException) {
$response = new JsonResponse(['message' => $exception->getMessage()], Response::HTTP_FORBIDDEN);
}
if ($exception instanceof ModelValidationException) {
$responseData = ['errors' => []];
foreach ($exception->getErrors() as $error) {
$responseData['errors'][] = [
'message' => $error->getMessage(),
'pointer' => $error->getPropertyPath()
];
}
$response = new JsonResponse($responseData, Response::HTTP_UNPROCESSABLE_ENTITY);
}
$event->setResponse($response);
}
/**
* @param ExceptionEvent $event
*/
private function handleException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
if ($exception instanceof HandlerFailedException) {
$exception = $exception->getPrevious();
}
if ($exception instanceof HttpExceptionInterface) {
$code = Response::HTTP_NOT_FOUND;
$message = "Resource was not found!";
} else {
$code = Response::HTTP_INTERNAL_SERVER_ERROR;
$message = "Oops... Something went wrong...";
try {
//$this->logger->error($exception->getMessage(), Log::SOURCE_GLOBAL);
} catch (ORMException | ModelValidationException $e) { // EntityManager is closed or log is incorrect
//
}
}
$this->session->set('error.code', $code);
$this->session->set('error.message', $message);
$response = new RedirectResponse($this->router->generate('site_error'));
$event->setResponse($response);
}
private function isApiException(ExceptionEvent $event)
{
return strpos($event->getRequest()->getRequestUri(), 'api') !== false;
}
}