comparison core/lib/Drupal/Core/EventSubscriber/FinalExceptionSubscriber.php @ 0:4c8ae668cc8c

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