src/Subscriber/ExceptionSubscriber.php line 86

Open in your IDE?
  1. <?php
  2. /**
  3.  * Listener class for exception handling
  4.  *
  5.  * PHP version 7.4
  6.  *
  7.  * @category   App
  8.  * @package    App\Listener
  9.  * @author     Momcilo Radotic <m.radotic@outlook.com>
  10.  * @copyright  2021 MoravaPro
  11.  * @license    MoravaPro
  12.  */
  13. namespace App\Subscriber;
  14. use App\Exception\ValidationFailedException;
  15. use App\Model\ResponseModel;
  16. use Psr\Log\LoggerInterface;
  17. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  18. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  19. use Symfony\Component\HttpFoundation\Exception\BadRequestException;
  20. use Symfony\Component\HttpFoundation\JsonResponse;
  21. use Symfony\Component\HttpFoundation\Request;
  22. use Symfony\Component\HttpFoundation\Response;
  23. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  24. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  25. use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
  26. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  27. use Symfony\Component\HttpKernel\KernelEvents;
  28. use Symfony\Component\Routing\Exception\MethodNotAllowedException;
  29. use Symfony\Component\Security\Core\Exception\AccessDeniedException;
  30. use Twig\Environment;
  31. /**
  32.  * Listener class for exception handling
  33.  *
  34.  * @category   App
  35.  * @package    App\Listener
  36.  */
  37. class ExceptionSubscriber implements EventSubscriberInterface
  38. {
  39.     /**
  40.      * @var ParameterBagInterface
  41.      */
  42.     private ParameterBagInterface $parameterBag;
  43.     /**
  44.      * @var LoggerInterface
  45.      */
  46.     private LoggerInterface $logger;
  47.     /**
  48.      * @var Environment
  49.      */
  50.     private Environment $twigEnvironment;
  51.     public function __construct(ParameterBagInterface $parameterBagLoggerInterface $loggerEnvironment $twigEnvironment)
  52.     {
  53.         $this->parameterBag $parameterBag;
  54.         $this->logger $logger;
  55.         $this->twigEnvironment $twigEnvironment;
  56.     }
  57.     /**
  58.      * {@inheritDoc}
  59.      */
  60.     public static function getSubscribedEvents()
  61.     {
  62.         return [
  63.             KernelEvents::EXCEPTION => [
  64.                 [
  65.                     'onKernelException',
  66.                     10
  67.                 ]
  68.             ]
  69.         ];
  70.     }
  71.     /**
  72.      * Handle application exception
  73.      *
  74.      * @param ExceptionEvent $event
  75.      */
  76.     public function onKernelException(ExceptionEvent $event)
  77.     {
  78.         $exception $event->getThrowable();
  79.         if ($exception instanceof ValidationFailedException) {
  80.             $responseModel = new ResponseModel();
  81.             $responseModel->setSuccess(false);
  82.             $responseModel->setData([
  83.                 'type' => 'validation_error',
  84.                 'message' => $exception->getMessage(),
  85.             ]);
  86.             $event->setResponse(new JsonResponse($responseModelResponse::HTTP_BAD_REQUEST));
  87.             return;
  88.         }
  89.         // TODO Remove this block ASAP, as it's EXTREMELY DANGEROUS
  90.         // It provides a false API response to the frontend during development
  91.         try {
  92.             // if environment dev, do nothing, display app error
  93.             if ($this->parameterBag->get('kernel.environment') == 'dev') {
  94.                 return ;
  95.             }
  96.         } catch (\Throwable $e) {
  97.             $this->logger->error($e);
  98.         }
  99.         $exception $event->getThrowable();
  100.         $this->logger->error($exception);
  101.         $isAjaxOrApi $this->checkIsAjaxOrApi($event->getRequest());
  102.         switch (get_class($exception)) {
  103.             case AccessDeniedException::class:
  104.                 $response $isAjaxOrApi
  105.                     $this->handleJsonErrorResponse(Response::HTTP_UNAUTHORIZED'Access denied')
  106.                     : $this->handleHttpErrorResponse(Response::HTTP_UNAUTHORIZED);
  107.                 break;
  108.             case BadRequestException::class:
  109.             case BadRequestHttpException::class:
  110.                 $response $isAjaxOrApi
  111.                     $this->handleJsonErrorResponse(Response::HTTP_BAD_REQUEST'Bad request data')
  112.                     : $this->handleHttpErrorResponse(Response::HTTP_BAD_REQUEST);
  113.                 break;
  114.             case NotFoundHttpException::class:
  115.             case MethodNotAllowedException::class:
  116.             case MethodNotAllowedHttpException::class:
  117.                 $response $isAjaxOrApi
  118.                     $this->handleJsonErrorResponse(Response::HTTP_NOT_FOUND'Not found')
  119.                     : $this->handleHttpErrorResponse(Response::HTTP_NOT_FOUND);
  120.                 break;
  121.             default:
  122.                 $response $isAjaxOrApi
  123.                     $this->handleJsonErrorResponse(Response::HTTP_INTERNAL_SERVER_ERROR'Internal server error')
  124.                     : $this->handleHttpErrorResponse(Response::HTTP_INTERNAL_SERVER_ERROR);
  125.         }
  126.         $event->setResponse($response);
  127.     }
  128.     /**
  129.      * Check type of request
  130.      *
  131.      * @param Request $request
  132.      *
  133.      * @return bool
  134.      */
  135.     private function checkIsAjaxOrApi(Request $request)
  136.     {
  137.         $accepts $request->get('Accept');
  138.         $contentType $request->headers->get('Content-Type');
  139.         $wantsJson preg_match('{^\s*application/json,\s*}'$accepts) || preg_match('{^\s*application/json\s*}'$contentType);
  140.         $isAjax $request->isXmlHttpRequest();
  141.         return $wantsJson || $isAjax;
  142.     }
  143.     /**
  144.      * Handle json error response
  145.      *
  146.      * @param int $errorCode
  147.      * @param string $errorMessage
  148.      *
  149.      * @return JsonResponse
  150.      */
  151.     private function handleJsonErrorResponse(int $errorCodestring $errorMessage) : JsonResponse
  152.     {
  153.         $response = new ResponseModel();
  154.         $response->setError($errorCode$errorMessage);
  155.         $jsonResponse = new JsonResponse();
  156.         $jsonResponse->setJson(json_encode($response));
  157.         $jsonResponse->setStatusCode($errorCode);
  158.         return $jsonResponse;
  159.     }
  160.     /**
  161.      * Handle http exception
  162.      *
  163.      * @param int $errorCode
  164.      *
  165.      * @return Response
  166.      */
  167.     private function handleHttpErrorResponse(int $errorCode) : Response
  168.     {
  169.         $httpResponse = new Response();
  170.         $httpResponse->setStatusCode($errorCode);
  171.         $httpResponse->setContent($this->renderErrorPage($errorCode));
  172.         return $httpResponse;
  173.     }
  174.     /**
  175.      * Render error html page
  176.      *
  177.      * @param int $errorCode
  178.      *
  179.      * @return string
  180.      */
  181.     private function renderErrorPage(int $errorCode) : string
  182.     {
  183.         try {
  184.             switch ($errorCode) {
  185.                 case Response::HTTP_UNAUTHORIZED:
  186.                     return $this->twigEnvironment->render('unauthorized.html.twig');
  187.                 case Response::HTTP_BAD_REQUEST:
  188.                     return $this->twigEnvironment->render('bad_request.html.twig');
  189.                 case Response::HTTP_NOT_FOUND:
  190.                     return $this->twigEnvironment->render('not_found.html.twig');
  191.                 default:
  192.                     return $this->twigEnvironment->render('server_error.html.twig');
  193.             }
  194.         } catch (\Throwable $exception) {
  195.             $this->logger->error($exception);
  196.             return '<html><head></head><body><p>Internal server error</p></body></body></head></html>';//@TODO handle this case!! Create some static html page
  197.         }
  198.     }
  199. }