You are here

class DefaultExceptionSubscriber in Zircon Profile 8

Same name and namespace in other branches
  1. 8.0 core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php \Drupal\Core\EventSubscriber\DefaultExceptionSubscriber

Last-chance handler for exceptions.

This handler will catch any exceptions not caught elsewhere and report them as an error page.

Hierarchy

Expanded class hierarchy of DefaultExceptionSubscriber

1 string reference to 'DefaultExceptionSubscriber'
core.services.yml in core/core.services.yml
core/core.services.yml
1 service uses DefaultExceptionSubscriber
exception.default in core/core.services.yml
Drupal\Core\EventSubscriber\DefaultExceptionSubscriber

File

core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php, line 28
Contains \Drupal\Core\EventSubscriber\DefaultExceptionSubscriber.

Namespace

Drupal\Core\EventSubscriber
View source
class DefaultExceptionSubscriber implements EventSubscriberInterface {
  use StringTranslationTrait;

  /**
   * @var string
   *
   * One of the error level constants defined in bootstrap.inc.
   */
  protected $errorLevel;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * Constructs a new DefaultExceptionSubscriber.
   *
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory.
   */
  public function __construct(ConfigFactoryInterface $config_factory) {
    $this->configFactory = $config_factory;
  }

  /**
   * Gets the configured error level.
   *
   * @return string
   */
  protected function getErrorLevel() {
    if (!isset($this->errorLevel)) {
      $this->errorLevel = $this->configFactory
        ->get('system.logging')
        ->get('error_level');
    }
    return $this->errorLevel;
  }

  /**
   * Handles any exception as a generic error page for HTML.
   *
   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
   *   The event to process.
   */
  protected function onHtml(GetResponseForExceptionEvent $event) {
    $exception = $event
      ->getException();
    $error = Error::decodeException($exception);

    // Display the message if the current error reporting level allows this type
    // of message to be displayed, and unconditionally in update.php.
    $message = '';
    if (error_displayable($error)) {

      // If error type is 'User notice' then treat it as debug information
      // instead of an error message.
      // @see debug()
      if ($error['%type'] == 'User notice') {
        $error['%type'] = 'Debug';
      }

      // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
      // in the message. This does not happen for (false) security.
      $root_length = strlen(DRUPAL_ROOT);
      if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) {
        $error['%file'] = substr($error['%file'], $root_length + 1);
      }
      unset($error['backtrace']);
      if ($this
        ->getErrorLevel() != ERROR_REPORTING_DISPLAY_VERBOSE) {

        // Without verbose logging, use a simple message.
        // We call SafeMarkup::format directly here, rather than use t() since
        // we are in the middle of error handling, and we don't want t() to
        // cause further errors.
        $message = SafeMarkup::format('%type: @message in %function (line %line of %file).', $error);
      }
      else {

        // With verbose logging, we will also include a backtrace.
        $backtrace_exception = $exception;
        while ($backtrace_exception
          ->getPrevious()) {
          $backtrace_exception = $backtrace_exception
            ->getPrevious();
        }
        $backtrace = $backtrace_exception
          ->getTrace();

        // First trace is the error itself, already contained in the message.
        // While the second trace is the error source and also contained in the
        // message, the message doesn't contain argument values, so we output it
        // once more in the backtrace.
        array_shift($backtrace);

        // Generate a backtrace containing only scalar argument values.
        $error['@backtrace'] = Error::formatBacktrace($backtrace);
        $message = SafeMarkup::format('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $error);
      }
    }
    $content = $this
      ->t('The website encountered an unexpected error. Please try again later.');
    $content .= $message ? '</br></br>' . $message : '';
    $response = new Response($content, 500);
    if ($exception instanceof HttpExceptionInterface) {
      $response
        ->setStatusCode($exception
        ->getStatusCode());
      $response->headers
        ->add($exception
        ->getHeaders());
    }
    else {
      $response
        ->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR, '500 Service unavailable (with message)');
    }
    $event
      ->setResponse($response);
  }

  /**
   * Handles any exception as a generic error page for JSON.
   *
   * @todo This should probably check the error reporting level.
   *
   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
   *   The event to process.
   */
  protected function onJson(GetResponseForExceptionEvent $event) {
    $exception = $event
      ->getException();
    $error = Error::decodeException($exception);

    // Display the message if the current error reporting level allows this type
    // of message to be displayed,
    $data = NULL;
    if (error_displayable($error) && ($message = $exception
      ->getMessage())) {
      $data = [
        'message' => sprintf('A fatal error occurred: %s', $message),
      ];
    }
    $response = new JsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR);
    if ($exception instanceof HttpExceptionInterface) {
      $response
        ->setStatusCode($exception
        ->getStatusCode());
      $response->headers
        ->add($exception
        ->getHeaders());
    }
    $event
      ->setResponse($response);
  }

  /**
   * Handles errors for this subscriber.
   *
   * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
   *   The event to process.
   */
  public function onException(GetResponseForExceptionEvent $event) {
    $format = $this
      ->getFormat($event
      ->getRequest());

    // If it's an unrecognized format, assume HTML.
    $method = 'on' . $format;
    if (!method_exists($this, $method)) {
      $method = 'onHtml';
    }
    $this
      ->{$method}($event);
  }

  /**
   * Gets the error-relevant format from the request.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The request object.
   *
   * @return string
   *   The format as which to treat the exception.
   */
  protected function getFormat(Request $request) {
    $format = $request->query
      ->get(MainContentViewSubscriber::WRAPPER_FORMAT, $request
      ->getRequestFormat());

    // These are all JSON errors for our purposes. Any special handling for
    // them can/should happen in earlier listeners if desired.
    if (in_array($format, [
      'drupal_modal',
      'drupal_dialog',
      'drupal_ajax',
    ])) {
      $format = 'json';
    }

    // Make an educated guess that any Accept header type that includes "json"
    // can probably handle a generic JSON response for errors. As above, for
    // any format this doesn't catch or that wants custom handling should
    // register its own exception listener.
    foreach ($request
      ->getAcceptableContentTypes() as $mime) {
      if (strpos($mime, 'html') === FALSE && strpos($mime, 'json') !== FALSE) {
        $format = 'json';
      }
    }
    return $format;
  }

  /**
   * Registers the methods in this class that should be listeners.
   *
   * @return array
   *   An array of event listener definitions.
   */
  public static function getSubscribedEvents() {
    $events[KernelEvents::EXCEPTION][] = [
      'onException',
      -256,
    ];
    return $events;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
DefaultExceptionSubscriber::$configFactory protected property The config factory.
DefaultExceptionSubscriber::$errorLevel protected property One of the error level constants defined in bootstrap.inc.
DefaultExceptionSubscriber::getErrorLevel protected function Gets the configured error level.
DefaultExceptionSubscriber::getFormat protected function Gets the error-relevant format from the request.
DefaultExceptionSubscriber::getSubscribedEvents public static function Registers the methods in this class that should be listeners. Overrides EventSubscriberInterface::getSubscribedEvents
DefaultExceptionSubscriber::onException public function Handles errors for this subscriber.
DefaultExceptionSubscriber::onHtml protected function Handles any exception as a generic error page for HTML.
DefaultExceptionSubscriber::onJson protected function Handles any exception as a generic error page for JSON.
DefaultExceptionSubscriber::__construct public function Constructs a new DefaultExceptionSubscriber.
StringTranslationTrait::$stringTranslation protected property The string translation service.
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.