Symfony: Custom error pages by bundle

08.10.2013 Web

I was looking for a solution to provide bundle based custom error pages with integrated fallback to the standard error pages provided by the framework.

The common way to provide custom error pages is to override the standard pages located in /vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Resources/views/Exception by placing a customized error.html.twig file in the /app/Resources/TwigBundle/views/Exception folder for instance. This works perfectly and applies application wide.

Preconditions

In this special case, I'd like to provide bundle based custom error pages without using the above-mentioned method. Additionally there should be a fallback if a particular error template is NOT provided by the bundle.

Extensions and service.xml

Since the common ExceptionController is provided as a service it is easy to delegate all calls to a custom controller, e.g. MyExceptionController. This could be done by overriding a global parameter setting in /src/Acme/DemoBundle/Resources/config/services.xml.

This needs that file to be loaded properly, which could be guaranteed by checking the default bundle extension which will be created automatically if you let your bundle be created by command line helpers (type php app/console generate:bundle --help at your command line prompt for more information).

The following code shows how things should be:

src/Acme/DemoBundle/DependencyInjection/AcmeDemoExtension.php

<?php
namespace Acme\DemoBundle\DependencyInjection;
 
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\Config\FileLocator;
 
class AcmeDemoExtension extends Extension
{
    public function load(array $configs, ContainerBuilder $container)
    {
        $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
        $loader->load('services.xml');
    }
}

After that we're able to tweek service.xml by overriding a default setting set in /vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml:

/vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml

With the following setting we point to our (still to be created) custom exception controller now:

/src/Acme/DemoBundle/Resources/config/services.xml

A custom exception controller

Now we've to provide the previously announced custom exception controller. Since the default exception controller does an excellent job, we're going to extend it's class to match our requirements. This will allow us to override the findTempalte-method which handles identifying the proper error page template.

src/Acme/DemoBundle/Controller/MyExceptionController

<?php
namespace Acme\DemoBundle\Controller;
 
use Symfony\Bundle\TwigBundle\Controller\ExceptionController as BaseController;
use Symfony\Bundle\FrameworkBundle\Templating\TemplateReference;
use Symfony\Component\HttpFoundation\Request;
 
class MyExceptionController extends BaseController {
  protected function findTemplate(Request $request, $format, $code, $debug) {
    // See web/app_dev.php  $kernel = new AppKernel('dev', <debug>) to change debug variable value;
    $name = $debug ? 'exception' : 'error';
 
    // try to find a template for the given format
    $template = new TemplateReference('DemoBundle', 'MyExceptions', $name, $format, 'twig');
    if ($this->templateExists($template)) {
      return $template;
    } else {
      return parent::findTemplate($request, $format, $code, $debug);
    }
  }
}

What it does

  1. MyExceptionController.php extends Symfony\Bundle\TwigBundle\Controller\ExceptionController (line 8).
  2. Override method protected function findTemplate(Request $request, $format, $code, $debug) (line 9).
  3. Preselection of the name of the template in dependance of $debug's value (line 11).
  4. Creating a new TemplateReference($bundle, $dir, $name, $format, $engine) to a template located at /Acme/DemoBundle/Resources/views/MyExceptions/error.html.twig. The path to the template is internally built (in first approximation) by substituting the variables in @$bundle/Resources/views/$dir/$name.$format.$engine (line 14).
  5. Check if our custom error page exists (line 15). Return it if it exists (line 16).
  6. Return the TemplateReference to the standard error page by calling method findTemplate of the base class if no suitable custom error page exists (line 18).