annotate core/lib/Drupal/Core/EventSubscriber/FinalExceptionSubscriber.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 129ea1e6d783
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\EventSubscriber;
Chris@0 4
Chris@17 5 use Drupal\Component\Render\FormattableMarkup;
Chris@0 6 use Drupal\Core\Config\ConfigFactoryInterface;
Chris@0 7 use Drupal\Core\StringTranslation\StringTranslationTrait;
Chris@0 8 use Drupal\Core\Utility\Error;
Chris@0 9 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
Chris@0 10 use Symfony\Component\HttpFoundation\Response;
Chris@0 11 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
Chris@0 12 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
Chris@0 13 use Symfony\Component\HttpKernel\KernelEvents;
Chris@0 14
Chris@0 15 /**
Chris@0 16 * Last-chance handler for exceptions: the final exception subscriber.
Chris@0 17 *
Chris@0 18 * This handler will catch any exceptions not caught elsewhere and report
Chris@0 19 * them as an error page.
Chris@0 20 *
Chris@0 21 * Each format has its own way of handling exceptions:
Chris@0 22 * - html: exception.default_html, exception.custom_page_html and
Chris@0 23 * exception.fast_404_html
Chris@0 24 * - json: exception.default_json
Chris@0 25 *
Chris@0 26 * And when the serialization module is installed, all serialization formats are
Chris@0 27 * handled by a single exception subscriber:: serialization.exception.default.
Chris@0 28 *
Chris@0 29 * This exception subscriber runs after all the above (it has a lower priority),
Chris@0 30 * which makes it the last-chance exception handler. It always sends a plain
Chris@0 31 * text response. If it's a displayable error and the error level is configured
Chris@0 32 * to be verbose, then a helpful backtrace is also printed.
Chris@0 33 */
Chris@0 34 class FinalExceptionSubscriber implements EventSubscriberInterface {
Chris@0 35 use StringTranslationTrait;
Chris@0 36
Chris@0 37 /**
Chris@0 38 * @var string
Chris@0 39 *
Chris@0 40 * One of the error level constants defined in bootstrap.inc.
Chris@0 41 */
Chris@0 42 protected $errorLevel;
Chris@0 43
Chris@0 44 /**
Chris@0 45 * The config factory.
Chris@0 46 *
Chris@0 47 * @var \Drupal\Core\Config\ConfigFactoryInterface
Chris@0 48 */
Chris@0 49 protected $configFactory;
Chris@0 50
Chris@0 51 /**
Chris@0 52 * Constructs a new FinalExceptionSubscriber.
Chris@0 53 *
Chris@0 54 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
Chris@0 55 * The configuration factory.
Chris@0 56 */
Chris@0 57 public function __construct(ConfigFactoryInterface $config_factory) {
Chris@0 58 $this->configFactory = $config_factory;
Chris@0 59 }
Chris@0 60
Chris@0 61 /**
Chris@0 62 * Gets the configured error level.
Chris@0 63 *
Chris@0 64 * @return string
Chris@0 65 */
Chris@0 66 protected function getErrorLevel() {
Chris@0 67 if (!isset($this->errorLevel)) {
Chris@0 68 $this->errorLevel = $this->configFactory->get('system.logging')->get('error_level');
Chris@0 69 }
Chris@0 70 return $this->errorLevel;
Chris@0 71 }
Chris@0 72
Chris@0 73 /**
Chris@0 74 * Handles exceptions for this subscriber.
Chris@0 75 *
Chris@0 76 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 77 * The event to process.
Chris@0 78 */
Chris@0 79 public function onException(GetResponseForExceptionEvent $event) {
Chris@0 80 $exception = $event->getException();
Chris@0 81 $error = Error::decodeException($exception);
Chris@0 82
Chris@0 83 // Display the message if the current error reporting level allows this type
Chris@0 84 // of message to be displayed, and unconditionally in update.php.
Chris@0 85 $message = '';
Chris@0 86 if ($this->isErrorDisplayable($error)) {
Chris@0 87 // If error type is 'User notice' then treat it as debug information
Chris@0 88 // instead of an error message.
Chris@0 89 // @see debug()
Chris@0 90 if ($error['%type'] == 'User notice') {
Chris@0 91 $error['%type'] = 'Debug';
Chris@0 92 }
Chris@0 93
Chris@0 94 $error = $this->simplifyFileInError($error);
Chris@0 95
Chris@0 96 unset($error['backtrace']);
Chris@0 97
Chris@0 98 if (!$this->isErrorLevelVerbose()) {
Chris@0 99 // Without verbose logging, use a simple message.
Chris@0 100
Chris@17 101 // We use \Drupal\Component\Render\FormattableMarkup directly here,
Chris@17 102 // rather than use t() since we are in the middle of error handling, and
Chris@17 103 // we don't want t() to cause further errors.
Chris@17 104 $message = new FormattableMarkup('%type: @message in %function (line %line of %file).', $error);
Chris@0 105 }
Chris@0 106 else {
Chris@0 107 // With verbose logging, we will also include a backtrace.
Chris@0 108
Chris@0 109 $backtrace_exception = $exception;
Chris@0 110 while ($backtrace_exception->getPrevious()) {
Chris@0 111 $backtrace_exception = $backtrace_exception->getPrevious();
Chris@0 112 }
Chris@0 113 $backtrace = $backtrace_exception->getTrace();
Chris@0 114 // First trace is the error itself, already contained in the message.
Chris@0 115 // While the second trace is the error source and also contained in the
Chris@0 116 // message, the message doesn't contain argument values, so we output it
Chris@0 117 // once more in the backtrace.
Chris@0 118 array_shift($backtrace);
Chris@0 119
Chris@0 120 // Generate a backtrace containing only scalar argument values.
Chris@0 121 $error['@backtrace'] = Error::formatBacktrace($backtrace);
Chris@17 122 $message = new FormattableMarkup('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $error);
Chris@0 123 }
Chris@0 124 }
Chris@0 125
Chris@0 126 $content = $this->t('The website encountered an unexpected error. Please try again later.');
Chris@0 127 $content .= $message ? '</br></br>' . $message : '';
Chris@0 128 $response = new Response($content, 500, ['Content-Type' => 'text/plain']);
Chris@0 129
Chris@0 130 if ($exception instanceof HttpExceptionInterface) {
Chris@0 131 $response->setStatusCode($exception->getStatusCode());
Chris@0 132 $response->headers->add($exception->getHeaders());
Chris@0 133 }
Chris@0 134 else {
Chris@0 135 $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR, '500 Service unavailable (with message)');
Chris@0 136 }
Chris@0 137
Chris@0 138 $event->setResponse($response);
Chris@0 139 }
Chris@0 140
Chris@0 141 /**
Chris@0 142 * {@inheritdoc}
Chris@0 143 */
Chris@0 144 public static function getSubscribedEvents() {
Chris@0 145 // Run as the final (very late) KernelEvents::EXCEPTION subscriber.
Chris@0 146 $events[KernelEvents::EXCEPTION][] = ['onException', -256];
Chris@0 147 return $events;
Chris@0 148 }
Chris@0 149
Chris@0 150 /**
Chris@0 151 * Checks whether the error level is verbose or not.
Chris@0 152 *
Chris@0 153 * @return bool
Chris@0 154 */
Chris@0 155 protected function isErrorLevelVerbose() {
Chris@0 156 return $this->getErrorLevel() === ERROR_REPORTING_DISPLAY_VERBOSE;
Chris@0 157 }
Chris@0 158
Chris@0 159 /**
Chris@0 160 * Wrapper for error_displayable().
Chris@0 161 *
Chris@0 162 * @param $error
Chris@0 163 * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
Chris@0 164 *
Chris@0 165 * @return bool
Chris@0 166 *
Chris@0 167 * @see \error_displayable
Chris@0 168 */
Chris@0 169 protected function isErrorDisplayable($error) {
Chris@0 170 return error_displayable($error);
Chris@0 171 }
Chris@0 172
Chris@0 173 /**
Chris@0 174 * Attempts to reduce error verbosity in the error message's file path.
Chris@0 175 *
Chris@0 176 * Attempts to reduce verbosity by removing DRUPAL_ROOT from the file path in
Chris@0 177 * the message. This does not happen for (false) security.
Chris@0 178 *
Chris@0 179 * @param $error
Chris@0 180 * Optional error to examine for ERROR_REPORTING_DISPLAY_SOME.
Chris@0 181 *
Chris@0 182 * @return
Chris@0 183 * The updated $error.
Chris@0 184 */
Chris@0 185 protected function simplifyFileInError($error) {
Chris@0 186 // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
Chris@0 187 // in the message. This does not happen for (false) security.
Chris@0 188 $root_length = strlen(DRUPAL_ROOT);
Chris@0 189 if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) {
Chris@0 190 $error['%file'] = substr($error['%file'], $root_length + 1);
Chris@0 191 }
Chris@0 192 return $error;
Chris@0 193 }
Chris@0 194
Chris@0 195 }