Monday, March 19, 2018

Symfony custom database exception

For Symfony2, when your database is down, the default server error 500 message is shown.
To instead show a more appropriate 503 service unavailable message, you need to create custom exception listener. While the custom exception listener will listen to all exceptions, you can specify exception type checks inside it.

In your services.yml you need to specify the listener:
> src\Acme\AppBundle\Resources\config\services.yml

services:
    kernel.listener.your_pdo_listener:
        class: Acme\AppBundle\EventListener\YourExceptionListener
        tags:
           - { name: kernel.event_listener, event: kernel.exception, method: onKernelException }

Now you need to create the listener class:
> src\Acme\AppBundle\EventListener\AcmeAppBundleListener.php

use Symfony\Component\HttpKernel\Event\GetResponseEvent,
    Symfony\Component\HttpKernel\KernelEvents;

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

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;

use PropelException;
// use DoctrineException;

class YourExceptionListener implements EventSubscriberInterface
{
        $env = $this->container->getParameter('kernel.environment');
        if ($env == 'dev' || $env == 'test') {
            // show default exception
            return;
        }

        $exception = $event->getException();

        // only customize propel or doctrine ie database exceptions
        // if (!$exception instanceof DoctrineException) {
        if (!$exception instanceof PropelException) {
            // show default exception
            return;
        }     

        $path = $event->getRequest()->getPathInfo();

        $response = new Response();

        // set status text
        $exception_msg = $exception->getMessage();
        if (stripos($exception_msg, 'Unable to open PDO connection') !== false) {
            $response->setStatusCode(Response::HTTP_SERVICE_UNAVAILABLE);
            $status_text = $this->container->getParameter("message.error.unable_to_connect");
        } else {
            // show default exception
            return;
        }

        // add exception code, if set
        $exception_code = $exception->getCode();
        if ($exception_code > 0) {
            $status_text .= " [".$exception_code."]";
        }

        $response->setContent(
            $this->templating->render(
                'AcmeAppBundle:Exception:exception.html.twig',
                [
                    'status_code' => $response->getStatusCode(),
                    'status_text' => $status_text
                ]
            )
        );

        $event->setResponse($response);
}

Add the message configuration:
> src\Acme\AppBundle\Resources\config\messages.yml

parameters:
    message.error.unable_to_connect:        "Unable to connect to service at the moment"


Create the exception template:
> src\Acme\AppBundle\Resources\views\Exception\exception.html.twig

<!DOCTYPE html>
<html>
<head>
    <meta charset="{{ _charset }}" />
    <title>An Error Occurred: {{ status_text }}</title>
</head>
<body>
<h1>Zork! An Error Occurred</h1>
<h2>The server returned a "{{ status_code }} {{ status_text }}".</h2>

<div>
    This should be temporary, but if not, please let us know what you were doing when this error occurred.
    We will fix it as soon as possible. Sorry for any inconvenience caused.
</div>
</body>
</html>

Stop your database or in your code throw DoctrineException or PropelException and you should see the new exception page.

Reference
https://stackoverflow.com/questions/29732630/catching-database-exceptions-in-symfony2

End of document. Thanks for reading.

No comments:

Post a Comment