3 namespace Drupal\Core\Utility;
5 use Drupal\Component\Render\FormattableMarkup;
6 use Drupal\Component\Utility\Xss;
7 use Drupal\Core\Database\DatabaseExceptionWrapper;
10 * Drupal error utility class.
15 * The error severity level.
22 * An array of blacklisted functions.
26 protected static $blacklistFunctions = ['debug', '_drupal_error_handler', '_drupal_exception_handler'];
29 * Decodes an exception and retrieves the correct caller.
31 * @param \Exception|\Throwable $exception
32 * The exception object that was thrown.
35 * An error in the format expected by _drupal_log_error().
37 public static function decodeException($exception) {
38 $message = $exception->getMessage();
40 $backtrace = $exception->getTrace();
41 // Add the line throwing the exception to the backtrace.
42 array_unshift($backtrace, ['line' => $exception->getLine(), 'file' => $exception->getFile()]);
44 // For PDOException errors, we try to return the initial caller,
45 // skipping internal functions of the database layer.
46 if ($exception instanceof \PDOException || $exception instanceof DatabaseExceptionWrapper) {
47 // The first element in the stack is the call, the second element gives us
48 // the caller. We skip calls that occurred in one of the classes of the
49 // database layer or in one of its global functions.
50 $db_functions = ['db_query', 'db_query_range'];
51 while (!empty($backtrace[1]) && ($caller = $backtrace[1]) &&
52 ((isset($caller['class']) && (strpos($caller['class'], 'Query') !== FALSE || strpos($caller['class'], 'Database') !== FALSE || strpos($caller['class'], 'PDO') !== FALSE)) ||
53 in_array($caller['function'], $db_functions))) {
54 // We remove that call.
55 array_shift($backtrace);
57 if (isset($exception->query_string, $exception->args)) {
58 $message .= ": " . $exception->query_string . "; " . print_r($exception->args, TRUE);
62 $caller = static::getLastCaller($backtrace);
65 '%type' => get_class($exception),
66 // The standard PHP exception handler considers that the exception message
67 // is plain-text. We mimic this behavior here.
68 '@message' => $message,
69 '%function' => $caller['function'],
70 '%file' => $caller['file'],
71 '%line' => $caller['line'],
72 'severity_level' => static::ERROR,
73 'backtrace' => $backtrace,
74 '@backtrace_string' => $exception->getTraceAsString(),
79 * Renders an exception error message without further exceptions.
81 * @param \Exception|\Throwable $exception
82 * The exception object that was thrown.
87 public static function renderExceptionSafe($exception) {
88 $decode = static::decodeException($exception);
89 $backtrace = $decode['backtrace'];
90 unset($decode['backtrace']);
92 array_shift($backtrace);
94 // Even though it is possible that this method is called on a public-facing
95 // site, it is only called when the exception handler itself threw an
96 // exception, which normally means that a code change caused the system to
97 // no longer function correctly (as opposed to a user-triggered error), so
98 // we assume that it is safe to include a verbose backtrace.
99 $decode['@backtrace'] = Error::formatBacktrace($backtrace);
100 return new FormattableMarkup('%type: @message in %function (line %line of %file). <pre class="backtrace">@backtrace</pre>', $decode);
104 * Gets the last caller from a backtrace.
106 * @param array $backtrace
107 * A standard PHP backtrace. Passed by reference.
110 * An associative array with keys 'file', 'line' and 'function'.
112 public static function getLastCaller(array &$backtrace) {
113 // Errors that occur inside PHP internal functions do not generate
114 // information about file and line. Ignore black listed functions.
115 while (($backtrace && !isset($backtrace[0]['line'])) ||
116 (isset($backtrace[1]['function']) && in_array($backtrace[1]['function'], static::$blacklistFunctions))) {
117 array_shift($backtrace);
120 // The first trace is the call itself.
121 // It gives us the line and the file of the last call.
122 $call = $backtrace[0];
124 // The second call gives us the function where the call originated.
125 if (isset($backtrace[1])) {
126 if (isset($backtrace[1]['class'])) {
127 $call['function'] = $backtrace[1]['class'] . $backtrace[1]['type'] . $backtrace[1]['function'] . '()';
130 $call['function'] = $backtrace[1]['function'] . '()';
134 $call['function'] = 'main()';
141 * Formats a backtrace into a plain-text string.
143 * The calls show values for scalar arguments and type names for complex ones.
145 * @param array $backtrace
146 * A standard PHP backtrace.
149 * A plain-text line-wrapped string ready to be put inside <pre>.
151 public static function formatBacktrace(array $backtrace) {
154 foreach ($backtrace as $trace) {
155 $call = ['function' => '', 'args' => []];
157 if (isset($trace['class'])) {
158 $call['function'] = $trace['class'] . $trace['type'] . $trace['function'];
160 elseif (isset($trace['function'])) {
161 $call['function'] = $trace['function'];
164 $call['function'] = 'main';
167 if (isset($trace['args'])) {
168 foreach ($trace['args'] as $arg) {
169 if (is_scalar($arg)) {
170 $call['args'][] = is_string($arg) ? '\'' . Xss::filter($arg) . '\'' : $arg;
173 $call['args'][] = ucfirst(gettype($arg));
179 if (isset($trace['line'])) {
180 $line = " (Line: {$trace['line']})";
183 $return .= $call['function'] . '(' . implode(', ', $call['args']) . ")$line\n";