<?php
/**
* Listener class for exception handling
*
* PHP version 7.4
*
* @category App
* @package App\Listener
* @author Momcilo Radotic <m.radotic@outlook.com>
* @copyright 2021 MoravaPro
* @license MoravaPro
*/
namespace App\Subscriber;
use App\Model\ResponseModel;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Exception\BadRequestException;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Twig\Environment;
/**
* Listener class for exception handling
*
* @category App
* @package App\Listener
*/
class ExceptionSubscriber implements EventSubscriberInterface
{
/**
* @var ParameterBagInterface
*/
private ParameterBagInterface $parameterBag;
/**
* @var LoggerInterface
*/
private LoggerInterface $logger;
/**
* @var Environment
*/
private Environment $twigEnvironment;
public function __construct(ParameterBagInterface $parameterBag, LoggerInterface $logger, Environment $twigEnvironment)
{
$this->parameterBag = $parameterBag;
$this->logger = $logger;
$this->twigEnvironment = $twigEnvironment;
}
/**
* {@inheritDoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => [
[
'onKernelException',
10
]
]
];
}
/**
* Handle application exception
*
* @param ExceptionEvent $event
*/
public function onKernelException(ExceptionEvent $event)
{
try {
// if environment dev, do nothing, display app error
if ($this->parameterBag->get('kernel.environment') == 'dev') {
return ;
}
} catch (\Throwable $e) {
$this->logger->error($e);
}
$exception = $event->getThrowable();
$this->logger->error($exception);
$isAjaxOrApi = $this->checkIsAjaxOrApi($event->getRequest());
switch (get_class($exception)) {
case AccessDeniedException::class:
$response = $isAjaxOrApi
? $this->handleJsonErrorResponse(Response::HTTP_UNAUTHORIZED, 'Access denied')
: $this->handleHttpErrorResponse(Response::HTTP_UNAUTHORIZED);
break;
case BadRequestException::class:
case BadRequestHttpException::class:
$response = $isAjaxOrApi
? $this->handleJsonErrorResponse(Response::HTTP_BAD_REQUEST, 'Bad request data')
: $this->handleHttpErrorResponse(Response::HTTP_BAD_REQUEST);
break;
case NotFoundHttpException::class:
case MethodNotAllowedException::class:
case MethodNotAllowedHttpException::class:
$response = $isAjaxOrApi
? $this->handleJsonErrorResponse(Response::HTTP_NOT_FOUND, 'Not found')
: $this->handleHttpErrorResponse(Response::HTTP_NOT_FOUND);
break;
default:
$response = $isAjaxOrApi
? $this->handleJsonErrorResponse(Response::HTTP_INTERNAL_SERVER_ERROR, 'Internal server error')
: $this->handleHttpErrorResponse(Response::HTTP_INTERNAL_SERVER_ERROR);
}
$event->setResponse($response);
}
/**
* Check type of request
*
* @param Request $request
*
* @return bool
*/
private function checkIsAjaxOrApi(Request $request)
{
$accepts = $request->get('Accept');
$contentType = $request->headers->get('Content-Type');
$wantsJson = preg_match('{^\s*application/json,\s*}', $accepts) || preg_match('{^\s*application/json\s*}', $contentType);
$isAjax = $request->isXmlHttpRequest();
return $wantsJson || $isAjax;
}
/**
* Handle json error response
*
* @param int $errorCode
* @param string $errorMessage
*
* @return JsonResponse
*/
private function handleJsonErrorResponse(int $errorCode, string $errorMessage) : JsonResponse
{
$response = new ResponseModel();
$response->setError($errorCode, $errorMessage);
$jsonResponse = new JsonResponse();
$jsonResponse->setJson(json_encode($response));
$jsonResponse->setStatusCode($errorCode);
return $jsonResponse;
}
/**
* Handle http exception
*
* @param int $errorCode
*
* @return Response
*/
private function handleHttpErrorResponse(int $errorCode) : Response
{
$httpResponse = new Response();
$httpResponse->setStatusCode($errorCode);
$httpResponse->setContent($this->renderErrorPage($errorCode));
return $httpResponse;
}
/**
* Render error html page
*
* @param int $errorCode
*
* @return string
*/
private function renderErrorPage(int $errorCode) : string
{
try {
switch ($errorCode) {
case Response::HTTP_UNAUTHORIZED:
return $this->twigEnvironment->render('unauthorized.html.twig');
case Response::HTTP_BAD_REQUEST:
return $this->twigEnvironment->render('bad_request.html.twig');
case Response::HTTP_NOT_FOUND:
return $this->twigEnvironment->render('not_found.html.twig');
default:
return $this->twigEnvironment->render('server_error.html.twig');
}
} catch (\Throwable $exception) {
$this->logger->error($exception);
return '<html><head></head><body><p>Internal server error</p></body></body></head></html>';//@TODO handle this case!! Create some static html page
}
}
}