PageRenderTime 42ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php

http://github.com/drupal/drupal
PHP | 242 lines | 116 code | 32 blank | 94 comment | 21 complexity | 66a3830d287f8ed13a7fb74ee1f80b60 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. <?php
  2. namespace Drupal\Core\EventSubscriber;
  3. use Drupal\Component\Utility\SafeMarkup;
  4. use Drupal\Core\Config\ConfigFactoryInterface;
  5. use Drupal\Core\StringTranslation\StringTranslationTrait;
  6. use Drupal\Core\Utility\Error;
  7. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  8. use Symfony\Component\HttpFoundation\JsonResponse;
  9. use Symfony\Component\HttpFoundation\Request;
  10. use Symfony\Component\HttpFoundation\Response;
  11. use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
  12. use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
  13. use Symfony\Component\HttpKernel\KernelEvents;
  14. /**
  15. * Last-chance handler for exceptions.
  16. *
  17. * This handler will catch any exceptions not caught elsewhere and report
  18. * them as an error page.
  19. */
  20. class DefaultExceptionSubscriber implements EventSubscriberInterface {
  21. use StringTranslationTrait;
  22. /**
  23. * @var string
  24. *
  25. * One of the error level constants defined in bootstrap.inc.
  26. */
  27. protected $errorLevel;
  28. /**
  29. * The config factory.
  30. *
  31. * @var \Drupal\Core\Config\ConfigFactoryInterface
  32. */
  33. protected $configFactory;
  34. /**
  35. * Constructs a new DefaultExceptionSubscriber.
  36. *
  37. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
  38. * The configuration factory.
  39. */
  40. public function __construct(ConfigFactoryInterface $config_factory) {
  41. $this->configFactory = $config_factory;
  42. }
  43. /**
  44. * Gets the configured error level.
  45. *
  46. * @return string
  47. */
  48. protected function getErrorLevel() {
  49. if (!isset($this->errorLevel)) {
  50. $this->errorLevel = $this->configFactory->get('system.logging')->get('error_level');
  51. }
  52. return $this->errorLevel;
  53. }
  54. /**
  55. * Handles any exception as a generic error page for HTML.
  56. *
  57. * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  58. * The event to process.
  59. */
  60. protected function onHtml(GetResponseForExceptionEvent $event) {
  61. $exception = $event->getException();
  62. $error = Error::decodeException($exception);
  63. // Display the message if the current error reporting level allows this type
  64. // of message to be displayed, and unconditionally in update.php.
  65. $message = '';
  66. if (error_displayable($error)) {
  67. // If error type is 'User notice' then treat it as debug information
  68. // instead of an error message.
  69. // @see debug()
  70. if ($error['%type'] == 'User notice') {
  71. $error['%type'] = 'Debug';
  72. }
  73. // Attempt to reduce verbosity by removing DRUPAL_ROOT from the file path
  74. // in the message. This does not happen for (false) security.
  75. $root_length = strlen(DRUPAL_ROOT);
  76. if (substr($error['%file'], 0, $root_length) == DRUPAL_ROOT) {
  77. $error['%file'] = substr($error['%file'], $root_length + 1);
  78. }
  79. unset($error['backtrace']);
  80. if ($this->getErrorLevel() != ERROR_REPORTING_DISPLAY_VERBOSE) {
  81. // Without verbose logging, use a simple message.
  82. // We call SafeMarkup::format directly here, rather than use t() since
  83. // we are in the middle of error handling, and we don't want t() to
  84. // cause further errors.
  85. $message = SafeMarkup::format('%type: @message in %function (line %line of %file).', $error);
  86. }
  87. else {
  88. // With verbose logging, we will also include a backtrace.
  89. $backtrace_exception = $exception;
  90. while ($backtrace_exception->getPrevious()) {
  91. $backtrace_exception = $backtrace_exception->getPrevious();
  92. }
  93. $backtrace = $backtrace_exception->getTrace();
  94. // First trace is the error itself, already contained in the message.
  95. // While the second trace is the error source and also contained in the
  96. // message, the message doesn't contain argument values, so we output it
  97. // once more in the backtrace.
  98. array_shift($backtrace);
  99. // Generate a backtrace containing only scalar argument values.
  100. $error['@backtrace'] = Error::formatBacktrace($backtrace);
  101. $message = SafeMarkup::format('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $error);
  102. }
  103. }
  104. $content = $this->t('The website encountered an unexpected error. Please try again later.');
  105. $content .= $message ? '</br></br>' . $message : '';
  106. $response = new Response($content, 500);
  107. if ($exception instanceof HttpExceptionInterface) {
  108. $response->setStatusCode($exception->getStatusCode());
  109. $response->headers->add($exception->getHeaders());
  110. }
  111. else {
  112. $response->setStatusCode(Response::HTTP_INTERNAL_SERVER_ERROR, '500 Service unavailable (with message)');
  113. }
  114. $event->setResponse($response);
  115. }
  116. /**
  117. * Handles any exception as a generic error page for JSON.
  118. *
  119. * @todo This should probably check the error reporting level.
  120. *
  121. * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  122. * The event to process.
  123. */
  124. protected function onJson(GetResponseForExceptionEvent $event) {
  125. $exception = $event->getException();
  126. $error = Error::decodeException($exception);
  127. // Display the message if the current error reporting level allows this type
  128. // of message to be displayed,
  129. $data = NULL;
  130. if (error_displayable($error) && $message = $exception->getMessage()) {
  131. $data = ['message' => sprintf('A fatal error occurred: %s', $message)];
  132. }
  133. $response = new JsonResponse($data, Response::HTTP_INTERNAL_SERVER_ERROR);
  134. if ($exception instanceof HttpExceptionInterface) {
  135. $response->setStatusCode($exception->getStatusCode());
  136. $response->headers->add($exception->getHeaders());
  137. }
  138. $event->setResponse($response);
  139. }
  140. /**
  141. * Handles an HttpExceptionInterface exception for unknown formats.
  142. *
  143. * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  144. * The event to process.
  145. */
  146. protected function onFormatUnknown(GetResponseForExceptionEvent $event) {
  147. /** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface|\Exception $exception */
  148. $exception = $event->getException();
  149. $response = new Response($exception->getMessage(), $exception->getStatusCode(), $exception->getHeaders());
  150. $event->setResponse($response);
  151. }
  152. /**
  153. * Handles errors for this subscriber.
  154. *
  155. * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
  156. * The event to process.
  157. */
  158. public function onException(GetResponseForExceptionEvent $event) {
  159. $format = $this->getFormat($event->getRequest());
  160. $exception = $event->getException();
  161. $method = 'on' . $format;
  162. if (!method_exists($this, $method)) {
  163. if ($exception instanceof HttpExceptionInterface) {
  164. $this->onFormatUnknown($event);
  165. }
  166. else {
  167. $this->onHtml($event);
  168. }
  169. return;
  170. }
  171. $this->$method($event);
  172. }
  173. /**
  174. * Gets the error-relevant format from the request.
  175. *
  176. * @param \Symfony\Component\HttpFoundation\Request $request
  177. * The request object.
  178. *
  179. * @return string
  180. * The format as which to treat the exception.
  181. */
  182. protected function getFormat(Request $request) {
  183. $format = $request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT, $request->getRequestFormat());
  184. // These are all JSON errors for our purposes. Any special handling for
  185. // them can/should happen in earlier listeners if desired.
  186. if (in_array($format, ['drupal_modal', 'drupal_dialog', 'drupal_ajax'])) {
  187. $format = 'json';
  188. }
  189. // Make an educated guess that any Accept header type that includes "json"
  190. // can probably handle a generic JSON response for errors. As above, for
  191. // any format this doesn't catch or that wants custom handling should
  192. // register its own exception listener.
  193. foreach ($request->getAcceptableContentTypes() as $mime) {
  194. if (strpos($mime, 'html') === FALSE && strpos($mime, 'json') !== FALSE) {
  195. $format = 'json';
  196. }
  197. }
  198. return $format;
  199. }
  200. /**
  201. * Registers the methods in this class that should be listeners.
  202. *
  203. * @return array
  204. * An array of event listener definitions.
  205. */
  206. public static function getSubscribedEvents() {
  207. $events[KernelEvents::EXCEPTION][] = ['onException', -256];
  208. return $events;
  209. }
  210. }