3

I make a listener for exception handling. Below is my code

services.yml

kernel.listener.prod_exception_listener:
    class: MyBundle\Listener\ExceptionListener
    tags:
        - { name: kernel.event_listener, event: kernel.exception, method: onKernelException }

ExceptionListener.php

<?php
namespace MyBundle\Listener;

use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

class ExceptionListener
{
    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        // no fatal exception goes here others are coming in this function
        // like 403,404,500 are coming in this block

    }
}

What additional work I need to do for fatal exceptions in production mode? Because in dev mode fatal errors are coming in listener.

2 Answers 2

2

I solved it the following way, in my services.yml

api_exception_subscriber:
    class: AppBundle\EventListener\ApiExceptionSubscriber
    arguments: ['%kernel.debug%', '@api.response_factory', '@logger']
    tags:
        - { name: kernel.event_subscriber }
api.response_factory:
    class: AppBundle\Api\ResponseFactory

my response factory look like:

<?php

namespace AppBundle\Api;

use Symfony\Component\HttpFoundation\JsonResponse;

class ResponseFactory
{
    public function createResponse(ApiProblem $apiProblem)
    {
        $data = $apiProblem->toArray();

        $response = new JsonResponse(
            $data,
            $apiProblem->getStatusCode()
        );
        $response->headers->set('Content-Type', 'application/json');

        return $response;
    }
} 

and the Api subscriper class

<?php

namespace AppBundle\EventListener;

use AppBundle\Api\ApiProblem;
use AppBundle\Api\ApiProblemException;
use AppBundle\Api\ResponseFactory;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

class ApiExceptionSubscriber implements EventSubscriberInterface
{
    private $debug;

    private $responseFactory;

    private $logger;

    public function __construct($debug, ResponseFactory $responseFactory, LoggerInterface $logger)
    {
        $this->debug = $debug;
        $this->responseFactory = $responseFactory;
        $this->logger = $logger;
    }

    public function onKernelException(GetResponseForExceptionEvent $event)
    {
        // only reply to /api URLs
        if (strpos($event->getRequest()->getPathInfo(), '/api') !== 0) {
            return;
        }

        $e = $event->getException();

        $statusCode = $e instanceof HttpExceptionInterface ? $e->getStatusCode() : 500;

        // allow 500 errors to be thrown
        if ($this->debug && $statusCode >= 500) {
            return;
        }

        $this->logException($e);

        if ($e instanceof ApiProblemException) {
            $apiProblem = $e->getApiProblem();
        } else {


            $apiProblem = new ApiProblem(
                $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) {
                $apiProblem->set('detail', $e->getMessage());
            }
        }

        $response = $this->responseFactory->createResponse($apiProblem);

        $event->setResponse($response);
    }

    public static function getSubscribedEvents()
    {
        return array(
            KernelEvents::EXCEPTION => 'onKernelException'
        );
    }

    /**
     * Adapted from the core Symfony exception handling in ExceptionListener
     *
     * @param \Exception $exception
     */
    private function logException(\Exception $exception)
    {
        $message = sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine());
        $isCritical = !$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500;
        $context = array('exception' => $exception);
        if ($isCritical) {
            $this->logger->critical($message, $context);
        } else {
            $this->logger->error($message, $context);
        }
    }
}
Sign up to request clarification or add additional context in comments.

Comments

1

Edit 2020: as of Symfony 5 this is no longer necessary

I have handled this by overriding Kernel::handle to call the ExceptionListener manually

public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true): Response
{
    try {
        return parent::handle($request, $type, $catch);
    } catch (\Exception $exception) {
        throw new \Exception("There was an issue booting the framework");
    } catch (\Throwable $throwable) {
        $exception = new FatalThrowableError($throwable);

        $event = new ExceptionEvent($this, $request, $type, $exception);
        /** @var ExceptionListener $exceptionListener */
        $exceptionListener = $this->container->get(ExceptionListener::class);
        $exceptionListener->onKernelException($event);

        return $event->getResponse();
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.