sentenza sentenza - 6 months ago 184
PHP Question

How to report error in production on Symfony2 with a custom ERROR 500 template and an EventListener?

I want to report my error messages on a custom error 500 template. So, I've followed the official symfony tutorial on overriding the default error templates:

app/
└─ Resources/
└─ TwigBundle/
└─ views/
└─ Exception/
├─ error404.html.twig
├─ error403.html.twig
├─ error.html.twig # All other HTML errors (including 500)
├─ error404.json.twig
├─ error403.json.twig
└─ error.json.twig # All other JSON errors (including 500)


And my
error500.html.twig
page template looks like that:

<div class=" details">
<h3>Oops! Something went wrong.</h3>
<p> We are fixing it! Please come back in a while.
<br/> TEST</p>
<div class="alert alert-danger display-hide" {{message ? 'style="display: block;"'}}>
<button class="close" data-close="alert"></button>
<span>{{message ? message | trans}}</span>
</div>
<p>
<a href="{{path('homepage')}}" class="btn red btn-outline"> OK, take me to the homepage </a>
<br> </p>
</div>


Now, I want to write a particular
message
parameter by using the ExceptionListener that listen onKernelException, as described on the Symfony cookbook:

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

class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
// You get the exception object from the received event
$exception = $event->getException();
$message = sprintf(
'My Error says: %s with code: %s',
$exception->getMessage(),
$exception->getCode()
);

// Customize your response object to display the exception details
$response = new Response();
$response->setContent($message);

// HttpExceptionInterface is a special type of exception that
// holds status code and header details
if ($exception instanceof HttpExceptionInterface) {
$response->setStatusCode($exception->getStatusCode());
$response->headers->replace($exception->getHeaders());
} else {
$response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR);
}

// Send the modified response object to the event
$event->setResponse($response);
}
}


I think that I'm only missing the proper way to set the correct template of the
Symfony\Component\HttpFoundation\Response
. Actually I've been able to intercept the kernel exception event but I return a classic HTML error page without any template.

I'm using PHP 7 and Symfony 2.8.4.

Answer

You need to render the exception template manually.

To do that, pass the templating service to your event listener:

services:
    app.exception_listener:
        class: AppBundle\EventListener\ExceptionListener
        arguments: [ '@templating' ] # Here
        tags:
            - { name: kernel.event_listener, event: kernel.exception }

Then, add the following in your ExceptionListener class:

use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
// ...

class ExceptionListener
{
    /** @var EngineInterface */
    private $templating;

    /**
     * @param EngineInterface $templating
     */
    public function __construct(EngineInterface $templating) 
    {
        $this->templating = $templating;
    }

    // ...
}

Then, use it to render your view:

public function onKernelException(GetResponseForExceptionEvent $event)
{
    // ...

    $response = $this->templating->renderResponse(
        'TwigBundle:Exception:error_500.html.twig',
        ['message' => $message]
    );

    $event->setResponse($response);
}

Note
Your custom (overridden) template should be correctly used, otherwise use 'TwigBundle/Exception/error_500.html.twig' as the first argument of $this->templating->renderResponse() rather than 'TwigBundle:Exception:error_500.html.twig'.

To switch the template used depending on the current environment

Copy the original template from the TwigBundle (that you want use in dev) and copy its content in a new app/Resources/TwigBundle/Exception/error_500_dev.html.twig.

Rename your custom template in error_500_prod.html.twig.

Inject the environment as argument to your service:

app.exception_listener:
    # ...
    arguments: [ '@templating', '%kernel.environment%' ]

Set it as ExceptionListener::$env in the constructor like done for the templating.

Then, use it to render the good template:

$response = $this->renderResponse(
    sprintf('TwigBundle:Exception:error_500_%s', $this->env),
    // ...
);