5

Has anyone developed a custom themed page for 500 errors on a Drupal 8 site? There is no maintenance offline template equivalent, and core out of the box simply delivers an unstyled stack trace. We've been toying around with a solution in .htaccess, or by hooking into Symfony's exception events.

Anyone had success with either of these approaches?

2 Answers 2

7

You may create custom exception subscriber as it is shown below.

Notice that I did not use Drupal theming engine to render the error page. Uncaught exception indicates that something really wrong has happened on a site. There is a great chance that some Drupal services are not available so rendering error page may fail as well.

Also keep in mind that exception subscriber won't catch fatal PHP errors because they are handled differently.

src/EventSubscriber/ExceptionSubscriber.php

<?php

namespace Drupal\example\EventSubscriber;

use Drupal\Core\EventSubscriber\DefaultExceptionSubscriber;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\KernelEvents;

/**
 * Last-chance handler for exceptions.
 *
 * This handler will catch any exceptions not caught elsewhere and send a themed
 * error page as a response.
 */
class ExceptionSubscriber extends DefaultExceptionSubscriber {

  /**
   * {@inheritdoc}
   */
  protected function onHtml(GetResponseForExceptionEvent $event) {
    if ($this->getErrorLevel() == ERROR_REPORTING_DISPLAY_VERBOSE) {
      parent::onHtml($event);
    }
    else {
      $content = file_get_contents(DRUPAL_ROOT . '/../500-error.html');
      $response = new Response($content, 500);
      $event->setResponse($response);
    }
  }

  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    // Catch the exception just before default exeception subscriber.
    // @see Drupal\Core\EventSubscriber\DefaultExceptionSubscriber::getSubscribedEvents()
    $events[KernelEvents::EXCEPTION][] = ['onException', -255];
    return $events;
  }

}

services.yml

services:
  example.exception_subscribtr:
    class: Drupal\example\EventSubscriber\ExceptionSubscriber
    arguments: ['@serializer', '%serializer.formats%']

    tags:
      - { name: event_subscriber }

Note that you need to have error reporting set to something other than verbose for this to work (unless you alter the code above). This can either be controlled in the CMS here: /admin/config/development/logging or it may be overridden in your settings.php file. For example, you might need to set

$config['system.logging']['error_level'] = 'hide';

Also note, that if your file_get_contents throws a fatal error, you will get the standard error for uncaught exceptions: "The website encountered an unexpected error. Please try again later."

Here's two ways you can trigger a 500 error for testing:

use Symfony\Component\HttpKernel\Exception\HttpException;

throw new HttpException(500);

or

/** @var \Drupal\Core\Template\TwigEnvironment $environment */
$environment = \Drupal::service('twig');
$environment->loadTemplate('this-template-does-not-exist.html.twig')->render([]);
4
  • 3
    In Drupal 8.4 DefaultExceptionHandler has moved to the serialization module.
    – oknate
    Oct 26, 2017 at 16:52
  • Also, this doesn't work for fatal errors, such as if you call a php function that doesn't exist.
    – oknate
    Oct 26, 2017 at 18:33
  • Hi, can you explain how can I test this? because it won't happen on php errors as you've said @oknate Nov 8, 2018 at 14:34
  • There is a correction in the arguments section in the services entry. It should be arguments: ['@serializer', '%serializer.formats%'] instead of arguments: ['@config.factory']
    – Binny
    Feb 12, 2021 at 4:51
0

Here is a newer version of @ya.tecks answer with the same logic but different classes and more uptodate with latest versions of Drupal.

<?php

namespace Drupal\example\EventSubscriber;

use Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;

/**
 * Last-chance handler for exceptions.
 *
 * This handler will catch any exceptions not caught elsewhere and send a themed
 * error page as a response.
 */
class ExceptionSubscriber extends HttpExceptionSubscriberBase {

  /**
   * {@inheritdoc}
   */
  protected static function getPriority() {
    // A very low priority so that custom handlers are almost certain to fire
    // before it, even if someone forgets to set a priority.
    return 1;
  }

  /**
   * {@inheritdoc}
   */
  protected function getHandledFormats() {
    return [
      'html',
    ];
  }

  /**
   * The default 500 content.
   *
   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
   *   The event to process.
   */
  public function on500(GetResponseForExceptionEvent $event) {
    $content = file_get_contents(DRUPAL_ROOT . '/../500-error.html');
    $response = new Response($content, 500);
    $event->setResponse($response);
  }

  /**
   * Handles errors for this subscriber.
   *
   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
   *   The event to process.
   */
  public function onException(GetResponseForExceptionEvent $event) {
    $exception = $event->getException();
    // Make the exception available for example when rendering a block.
    if (preg_match('/5[0-9][0-9]/', $exception->getStatusCode())) {
      $method = 'on' . $exception->getStatusCode();

      if (method_exists($this, $method)) {
        $this->$method($event);
      }

      if (!method_exists($this, $method)) {
        $this->on500($event);
      }
    }
  }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.