8

In /core/includes/errors.inc, when there is a fatal error it outputs a response with the message "The website encountered an unexpected error. Please try again later." and a back-trace, if error_level is set to verbose, and without the back-trace otherwise.

It seems like at this point, there's no way, with an event subscriber to capture the response and alter it.

Is that correct?

Or is there a way to wrap the output from line 268 of errors.inc in Drupal 8.4 with markup?

Note, there's another answer that works if it's not a fatal error, but that doesn't offer a complete solution.

Also, I looked into .htaccess and Apache ErrorDocument directive, but that was misguided. Once Apache passes off to PHP, it can no longer use that directive. That's for real server errors.

4

Symfony's HttpKernel uses the event dispatcher to allow subscribers to handle exceptions and send custom responses. Only if no subscriber does so will it throw the exception further up and let it reach the global error handler. (At which point it's indeed too late to do anything else.)

Drupal already comes with such a subscriber (or rather several of them, which check particular conditions) for HTTP exceptions (These are implementors of \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface, such as NotFoundHttpException.)

If you absolutely need to intercept non-HTTP exceptions wherever they happen, and return custom responses, you can create your own subscriber (see Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase), and give it a very low priority (< -128) to avoid interfering with the core HTTP exception subscribers.

However, architecture-wise I would suggest this is not a good way to go. If you have a controller that triggers a particular exception, then it should catch it and throw the appropriate HTTP exception instead. This also requires much, much less code than messing around with event subscribers. ;)

If need be, you can even throw a ServiceUnavailableHttpException (503) or set a custom status like new HttpException(500) if there is no class for it.

Drupal core's exception subscribers don't actually handle 5xx errors, but you can create your own that does, which would probably look something like the following:

class Custom5xxHtmlSubscriber extends HttpExceptionSubscriberBase {
  public function on5xx(GetResponseForExceptionEvent $event) {
     // Do stuff
     (new Response())->send();
  }

With a corresponding service definition like the following:

  custom5xxsubscriber:
    class: ...
    tags:
      - { name: event_subscriber }
3
  • Theoretically, this is true but in practice, it's not working with 5xx errors which come from fatal errors. is this true?
    – Yuseferi
    Jul 20 at 6:08
  • Yes, there are fatal errors in PHP which cannot be intercepted, even with custom error handling, and the server will generally serve its own 5xx response for those (which might be customized outside of PHP through nginx/apache config). This answer only deals with HTTP 5xx responses generated inside PHP. Jul 20 at 12:12
  • thanks for the explaination. but I guess if in fatals PHP still generate 5xx responses. for the sample, just try to change the crednetial to connect to database. you see still php return 500 to the browser.
    – Yuseferi
    Jul 20 at 14:22
0

you can use https://www.drupal.org/project/custom_500_error module.

go to the setting and enter your custom message and markups which you want.

after that on all 500 errors, instead of the default message your customized message and markup will be shown.

0

You can generate custom 500 error responses by implementing a custom error handler and a custom exception handler, depending upon your needs, in your MODULE.module script.

Custom Unhandled Exception Handler

To catch any unhandled exceptions that would typically bubble up to drupal_register_shutdown_function and halt processing on the spot (no more "The website encountered an unexpected error. Please try again later." error):

// Set a custom exception handler.
set_exception_handler('MODULE_exception_handler');

/**
 * Handles uncaught exceptions.
 *
 * @param mixed $exception
 *   Exception that was uncaught.
 */
function MODULE_exception_handler(\Throwable $exception) {
  $exceptionType = get_class($exception);
  $exceptionMessage = $exception->getMessage();

  $content = "<h1>An error occurred.</h1>
    Error Type: $exceptionType<br>
    Message: $exceptionMessage<br>";

  echo $content;
}

Custom Error Handler

This also catches errors that would normally bubble up, though you must use exit to halt processing. If you do not exit, then the messages will stack until Drupal fires the shutdown function.

// Set a custom error handler.
set_error_handler('MODULE_error_handler');

/**
 * Provides a top-level error handler.
 *
 * @param int $errno
 *   Level of the error raised.
 * @param string $errstr
 *   Error message.
 * @param string $errfile
 *   Filename that the error was raised in.
 * @param int $errline
 *   Line number where the error was raised.
 *
 * @return bool
 *   True to halt additional PHP processing, false otherwise.
 */
function MODULE_error_handler(int $errno, string $errstr, string $errfile = NULL, int $errline = NULL): bool {
  // $errstr may need to be escaped:
  $errstr = htmlspecialchars($errstr);

  switch ($errno) {
    case \E_USER_ERROR:
      echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
      echo "  Fatal error on line $errline in file $errfile";
      echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
      echo "Aborting...<br />\n";
      // Immediately stop all additional processing.
      exit(1);

    case \E_USER_WARNING:
      echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
      // Uncomment to immediately stop all additional processing.
      // exit(1);
      break;

    case \E_USER_NOTICE:
      echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
      // Uncomment to immediately stop all additional processing.
      // exit(1);
      break;

    default:
      echo "Unknown error type: [$errno] $errstr<br />\n";
      // Uncomment to immediately stop all additional processing.
      // exit(1);
      break;
  }

  /* Don't execute PHP internal error handler */
  return true;
}

PLEASE NOTE: In the grand flow of things, the custom error handler is executed before the custom exception handler. Keep that in mind when deciding which one to use and when to call exit in the error handler.

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.