1 | 1 |
deleted file mode 100644 |
... | ... |
@@ -1,715 +0,0 @@ |
1 |
-<?php |
|
2 |
- |
|
3 |
-/* |
|
4 |
- * This file is part of the Symfony package. |
|
5 |
- * |
|
6 |
- * (c) Fabien Potencier <fabien@symfony.com> |
|
7 |
- * |
|
8 |
- * For the full copyright and license information, please view the LICENSE |
|
9 |
- * file that was distributed with this source code. |
|
10 |
- */ |
|
11 |
- |
|
12 |
-namespace Symfony\Component\Debug; |
|
13 |
- |
|
14 |
-use Psr\Log\LoggerInterface; |
|
15 |
-use Psr\Log\LogLevel; |
|
16 |
-use Symfony\Component\Debug\Exception\FatalErrorException; |
|
17 |
-use Symfony\Component\Debug\Exception\FatalThrowableError; |
|
18 |
-use Symfony\Component\Debug\Exception\FlattenException; |
|
19 |
-use Symfony\Component\Debug\Exception\OutOfMemoryException; |
|
20 |
-use Symfony\Component\Debug\Exception\SilencedErrorContext; |
|
21 |
-use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; |
|
22 |
-use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; |
|
23 |
-use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; |
|
24 |
-use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; |
|
25 |
- |
|
26 |
-/** |
|
27 |
- * A generic ErrorHandler for the PHP engine. |
|
28 |
- * |
|
29 |
- * Provides five bit fields that control how errors are handled: |
|
30 |
- * - thrownErrors: errors thrown as \ErrorException |
|
31 |
- * - loggedErrors: logged errors, when not @-silenced |
|
32 |
- * - scopedErrors: errors thrown or logged with their local context |
|
33 |
- * - tracedErrors: errors logged with their stack trace |
|
34 |
- * - screamedErrors: never @-silenced errors |
|
35 |
- * |
|
36 |
- * Each error level can be logged by a dedicated PSR-3 logger object. |
|
37 |
- * Screaming only applies to logging. |
|
38 |
- * Throwing takes precedence over logging. |
|
39 |
- * Uncaught exceptions are logged as E_ERROR. |
|
40 |
- * E_DEPRECATED and E_USER_DEPRECATED levels never throw. |
|
41 |
- * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. |
|
42 |
- * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. |
|
43 |
- * As errors have a performance cost, repeated errors are all logged, so that the developer |
|
44 |
- * can see them and weight them as more important to fix than others of the same level. |
|
45 |
- * |
|
46 |
- * @author Nicolas Grekas <p@tchwork.com> |
|
47 |
- * @author Grégoire Pineau <lyrixx@lyrixx.info> |
|
48 |
- * |
|
49 |
- * @final since Symfony 4.3 |
|
50 |
- */ |
|
51 |
-class ErrorHandler |
|
52 |
-{ |
|
53 |
- private $levels = [ |
|
54 |
- E_DEPRECATED => 'Deprecated', |
|
55 |
- E_USER_DEPRECATED => 'User Deprecated', |
|
56 |
- E_NOTICE => 'Notice', |
|
57 |
- E_USER_NOTICE => 'User Notice', |
|
58 |
- E_STRICT => 'Runtime Notice', |
|
59 |
- E_WARNING => 'Warning', |
|
60 |
- E_USER_WARNING => 'User Warning', |
|
61 |
- E_COMPILE_WARNING => 'Compile Warning', |
|
62 |
- E_CORE_WARNING => 'Core Warning', |
|
63 |
- E_USER_ERROR => 'User Error', |
|
64 |
- E_RECOVERABLE_ERROR => 'Catchable Fatal Error', |
|
65 |
- E_COMPILE_ERROR => 'Compile Error', |
|
66 |
- E_PARSE => 'Parse Error', |
|
67 |
- E_ERROR => 'Error', |
|
68 |
- E_CORE_ERROR => 'Core Error', |
|
69 |
- ]; |
|
70 |
- |
|
71 |
- private $loggers = [ |
|
72 |
- E_DEPRECATED => [null, LogLevel::INFO], |
|
73 |
- E_USER_DEPRECATED => [null, LogLevel::INFO], |
|
74 |
- E_NOTICE => [null, LogLevel::WARNING], |
|
75 |
- E_USER_NOTICE => [null, LogLevel::WARNING], |
|
76 |
- E_STRICT => [null, LogLevel::WARNING], |
|
77 |
- E_WARNING => [null, LogLevel::WARNING], |
|
78 |
- E_USER_WARNING => [null, LogLevel::WARNING], |
|
79 |
- E_COMPILE_WARNING => [null, LogLevel::WARNING], |
|
80 |
- E_CORE_WARNING => [null, LogLevel::WARNING], |
|
81 |
- E_USER_ERROR => [null, LogLevel::CRITICAL], |
|
82 |
- E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL], |
|
83 |
- E_COMPILE_ERROR => [null, LogLevel::CRITICAL], |
|
84 |
- E_PARSE => [null, LogLevel::CRITICAL], |
|
85 |
- E_ERROR => [null, LogLevel::CRITICAL], |
|
86 |
- E_CORE_ERROR => [null, LogLevel::CRITICAL], |
|
87 |
- ]; |
|
88 |
- |
|
89 |
- private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED |
|
90 |
- private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED |
|
91 |
- private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE |
|
92 |
- private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE |
|
93 |
- private $loggedErrors = 0; |
|
94 |
- private $traceReflector; |
|
95 |
- |
|
96 |
- private $isRecursive = 0; |
|
97 |
- private $isRoot = false; |
|
98 |
- private $exceptionHandler; |
|
99 |
- private $bootstrappingLogger; |
|
100 |
- |
|
101 |
- private static $reservedMemory; |
|
102 |
- private static $toStringException = null; |
|
103 |
- private static $silencedErrorCache = []; |
|
104 |
- private static $silencedErrorCount = 0; |
|
105 |
- private static $exitCode = 0; |
|
106 |
- |
|
107 |
- /** |
|
108 |
- * Registers the error handler. |
|
109 |
- * |
|
110 |
- * @param self|null $handler The handler to register |
|
111 |
- * @param bool $replace Whether to replace or not any existing handler |
|
112 |
- * |
|
113 |
- * @return self The registered error handler |
|
114 |
- */ |
|
115 |
- public static function register(self $handler = null, $replace = true) |
|
116 |
- { |
|
117 |
- if (null === self::$reservedMemory) { |
|
118 |
- self::$reservedMemory = str_repeat('x', 10240); |
|
119 |
- register_shutdown_function(__CLASS__.'::handleFatalError'); |
|
120 |
- } |
|
121 |
- |
|
122 |
- if ($handlerIsNew = null === $handler) { |
|
123 |
- $handler = new static(); |
|
124 |
- } |
|
125 |
- |
|
126 |
- if (null === $prev = set_error_handler([$handler, 'handleError'])) { |
|
127 |
- restore_error_handler(); |
|
128 |
- // Specifying the error types earlier would expose us to https://bugs.php.net/63206 |
|
129 |
- set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors); |
|
130 |
- $handler->isRoot = true; |
|
131 |
- } |
|
132 |
- |
|
133 |
- if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) { |
|
134 |
- $handler = $prev[0]; |
|
135 |
- $replace = false; |
|
136 |
- } |
|
137 |
- if (!$replace && $prev) { |
|
138 |
- restore_error_handler(); |
|
139 |
- $handlerIsRegistered = \is_array($prev) && $handler === $prev[0]; |
|
140 |
- } else { |
|
141 |
- $handlerIsRegistered = true; |
|
142 |
- } |
|
143 |
- if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) { |
|
144 |
- restore_exception_handler(); |
|
145 |
- if (!$handlerIsRegistered) { |
|
146 |
- $handler = $prev[0]; |
|
147 |
- } elseif ($handler !== $prev[0] && $replace) { |
|
148 |
- set_exception_handler([$handler, 'handleException']); |
|
149 |
- $p = $prev[0]->setExceptionHandler(null); |
|
150 |
- $handler->setExceptionHandler($p); |
|
151 |
- $prev[0]->setExceptionHandler($p); |
|
152 |
- } |
|
153 |
- } else { |
|
154 |
- $handler->setExceptionHandler($prev); |
|
155 |
- } |
|
156 |
- |
|
157 |
- $handler->throwAt(E_ALL & $handler->thrownErrors, true); |
|
158 |
- |
|
159 |
- return $handler; |
|
160 |
- } |
|
161 |
- |
|
162 |
- public function __construct(BufferingLogger $bootstrappingLogger = null) |
|
163 |
- { |
|
164 |
- if ($bootstrappingLogger) { |
|
165 |
- $this->bootstrappingLogger = $bootstrappingLogger; |
|
166 |
- $this->setDefaultLogger($bootstrappingLogger); |
|
167 |
- } |
|
168 |
- $this->traceReflector = new \ReflectionProperty('Exception', 'trace'); |
|
169 |
- $this->traceReflector->setAccessible(true); |
|
170 |
- } |
|
171 |
- |
|
172 |
- /** |
|
173 |
- * Sets a logger to non assigned errors levels. |
|
174 |
- * |
|
175 |
- * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels |
|
176 |
- * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants |
|
177 |
- * @param bool $replace Whether to replace or not any existing logger |
|
178 |
- */ |
|
179 |
- public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false) |
|
180 |
- { |
|
181 |
- $loggers = []; |
|
182 |
- |
|
183 |
- if (\is_array($levels)) { |
|
184 |
- foreach ($levels as $type => $logLevel) { |
|
185 |
- if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { |
|
186 |
- $loggers[$type] = [$logger, $logLevel]; |
|
187 |
- } |
|
188 |
- } |
|
189 |
- } else { |
|
190 |
- if (null === $levels) { |
|
191 |
- $levels = E_ALL; |
|
192 |
- } |
|
193 |
- foreach ($this->loggers as $type => $log) { |
|
194 |
- if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { |
|
195 |
- $log[0] = $logger; |
|
196 |
- $loggers[$type] = $log; |
|
197 |
- } |
|
198 |
- } |
|
199 |
- } |
|
200 |
- |
|
201 |
- $this->setLoggers($loggers); |
|
202 |
- } |
|
203 |
- |
|
204 |
- /** |
|
205 |
- * Sets a logger for each error level. |
|
206 |
- * |
|
207 |
- * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map |
|
208 |
- * |
|
209 |
- * @return array The previous map |
|
210 |
- * |
|
211 |
- * @throws \InvalidArgumentException |
|
212 |
- */ |
|
213 |
- public function setLoggers(array $loggers) |
|
214 |
- { |
|
215 |
- $prevLogged = $this->loggedErrors; |
|
216 |
- $prev = $this->loggers; |
|
217 |
- $flush = []; |
|
218 |
- |
|
219 |
- foreach ($loggers as $type => $log) { |
|
220 |
- if (!isset($prev[$type])) { |
|
221 |
- throw new \InvalidArgumentException('Unknown error type: '.$type); |
|
222 |
- } |
|
223 |
- if (!\is_array($log)) { |
|
224 |
- $log = [$log]; |
|
225 |
- } elseif (!\array_key_exists(0, $log)) { |
|
226 |
- throw new \InvalidArgumentException('No logger provided'); |
|
227 |
- } |
|
228 |
- if (null === $log[0]) { |
|
229 |
- $this->loggedErrors &= ~$type; |
|
230 |
- } elseif ($log[0] instanceof LoggerInterface) { |
|
231 |
- $this->loggedErrors |= $type; |
|
232 |
- } else { |
|
233 |
- throw new \InvalidArgumentException('Invalid logger provided'); |
|
234 |
- } |
|
235 |
- $this->loggers[$type] = $log + $prev[$type]; |
|
236 |
- |
|
237 |
- if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { |
|
238 |
- $flush[$type] = $type; |
|
239 |
- } |
|
240 |
- } |
|
241 |
- $this->reRegister($prevLogged | $this->thrownErrors); |
|
242 |
- |
|
243 |
- if ($flush) { |
|
244 |
- foreach ($this->bootstrappingLogger->cleanLogs() as $log) { |
|
245 |
- $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR; |
|
246 |
- if (!isset($flush[$type])) { |
|
247 |
- $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); |
|
248 |
- } elseif ($this->loggers[$type][0]) { |
|
249 |
- $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); |
|
250 |
- } |
|
251 |
- } |
|
252 |
- } |
|
253 |
- |
|
254 |
- return $prev; |
|
255 |
- } |
|
256 |
- |
|
257 |
- /** |
|
258 |
- * Sets a user exception handler. |
|
259 |
- * |
|
260 |
- * @param callable $handler A handler that will be called on Exception |
|
261 |
- * |
|
262 |
- * @return callable|null The previous exception handler |
|
263 |
- */ |
|
264 |
- public function setExceptionHandler(callable $handler = null) |
|
265 |
- { |
|
266 |
- $prev = $this->exceptionHandler; |
|
267 |
- $this->exceptionHandler = $handler; |
|
268 |
- |
|
269 |
- return $prev; |
|
270 |
- } |
|
271 |
- |
|
272 |
- /** |
|
273 |
- * Sets the PHP error levels that throw an exception when a PHP error occurs. |
|
274 |
- * |
|
275 |
- * @param int $levels A bit field of E_* constants for thrown errors |
|
276 |
- * @param bool $replace Replace or amend the previous value |
|
277 |
- * |
|
278 |
- * @return int The previous value |
|
279 |
- */ |
|
280 |
- public function throwAt($levels, $replace = false) |
|
281 |
- { |
|
282 |
- $prev = $this->thrownErrors; |
|
283 |
- $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; |
|
284 |
- if (!$replace) { |
|
285 |
- $this->thrownErrors |= $prev; |
|
286 |
- } |
|
287 |
- $this->reRegister($prev | $this->loggedErrors); |
|
288 |
- |
|
289 |
- return $prev; |
|
290 |
- } |
|
291 |
- |
|
292 |
- /** |
|
293 |
- * Sets the PHP error levels for which local variables are preserved. |
|
294 |
- * |
|
295 |
- * @param int $levels A bit field of E_* constants for scoped errors |
|
296 |
- * @param bool $replace Replace or amend the previous value |
|
297 |
- * |
|
298 |
- * @return int The previous value |
|
299 |
- */ |
|
300 |
- public function scopeAt($levels, $replace = false) |
|
301 |
- { |
|
302 |
- $prev = $this->scopedErrors; |
|
303 |
- $this->scopedErrors = (int) $levels; |
|
304 |
- if (!$replace) { |
|
305 |
- $this->scopedErrors |= $prev; |
|
306 |
- } |
|
307 |
- |
|
308 |
- return $prev; |
|
309 |
- } |
|
310 |
- |
|
311 |
- /** |
|
312 |
- * Sets the PHP error levels for which the stack trace is preserved. |
|
313 |
- * |
|
314 |
- * @param int $levels A bit field of E_* constants for traced errors |
|
315 |
- * @param bool $replace Replace or amend the previous value |
|
316 |
- * |
|
317 |
- * @return int The previous value |
|
318 |
- */ |
|
319 |
- public function traceAt($levels, $replace = false) |
|
320 |
- { |
|
321 |
- $prev = $this->tracedErrors; |
|
322 |
- $this->tracedErrors = (int) $levels; |
|
323 |
- if (!$replace) { |
|
324 |
- $this->tracedErrors |= $prev; |
|
325 |
- } |
|
326 |
- |
|
327 |
- return $prev; |
|
328 |
- } |
|
329 |
- |
|
330 |
- /** |
|
331 |
- * Sets the error levels where the @-operator is ignored. |
|
332 |
- * |
|
333 |
- * @param int $levels A bit field of E_* constants for screamed errors |
|
334 |
- * @param bool $replace Replace or amend the previous value |
|
335 |
- * |
|
336 |
- * @return int The previous value |
|
337 |
- */ |
|
338 |
- public function screamAt($levels, $replace = false) |
|
339 |
- { |
|
340 |
- $prev = $this->screamedErrors; |
|
341 |
- $this->screamedErrors = (int) $levels; |
|
342 |
- if (!$replace) { |
|
343 |
- $this->screamedErrors |= $prev; |
|
344 |
- } |
|
345 |
- |
|
346 |
- return $prev; |
|
347 |
- } |
|
348 |
- |
|
349 |
- /** |
|
350 |
- * Re-registers as a PHP error handler if levels changed. |
|
351 |
- */ |
|
352 |
- private function reRegister($prev) |
|
353 |
- { |
|
354 |
- if ($prev !== $this->thrownErrors | $this->loggedErrors) { |
|
355 |
- $handler = set_error_handler('var_dump'); |
|
356 |
- $handler = \is_array($handler) ? $handler[0] : null; |
|
357 |
- restore_error_handler(); |
|
358 |
- if ($handler === $this) { |
|
359 |
- restore_error_handler(); |
|
360 |
- if ($this->isRoot) { |
|
361 |
- set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors); |
|
362 |
- } else { |
|
363 |
- set_error_handler([$this, 'handleError']); |
|
364 |
- } |
|
365 |
- } |
|
366 |
- } |
|
367 |
- } |
|
368 |
- |
|
369 |
- /** |
|
370 |
- * Handles errors by filtering then logging them according to the configured bit fields. |
|
371 |
- * |
|
372 |
- * @param int $type One of the E_* constants |
|
373 |
- * @param string $message |
|
374 |
- * @param string $file |
|
375 |
- * @param int $line |
|
376 |
- * |
|
377 |
- * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself |
|
378 |
- * |
|
379 |
- * @throws \ErrorException When $this->thrownErrors requests so |
|
380 |
- * |
|
381 |
- * @internal |
|
382 |
- */ |
|
383 |
- public function handleError($type, $message, $file, $line) |
|
384 |
- { |
|
385 |
- // @deprecated to be removed in Symfony 5.0 |
|
386 |
- if (\PHP_VERSION_ID >= 70300 && $message && '"' === $message[0] && 0 === strpos($message, '"continue') && preg_match('/^"continue(?: \d++)?" targeting switch is equivalent to "break(?: \d++)?"\. Did you mean to use "continue(?: \d++)?"\?$/', $message)) { |
|
387 |
- $type = E_DEPRECATED; |
|
388 |
- } |
|
389 |
- |
|
390 |
- // Level is the current error reporting level to manage silent error. |
|
391 |
- $level = error_reporting(); |
|
392 |
- $silenced = 0 === ($level & $type); |
|
393 |
- // Strong errors are not authorized to be silenced. |
|
394 |
- $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; |
|
395 |
- $log = $this->loggedErrors & $type; |
|
396 |
- $throw = $this->thrownErrors & $type & $level; |
|
397 |
- $type &= $level | $this->screamedErrors; |
|
398 |
- |
|
399 |
- if (!$type || (!$log && !$throw)) { |
|
400 |
- return !$silenced && $type && $log; |
|
401 |
- } |
|
402 |
- $scope = $this->scopedErrors & $type; |
|
403 |
- |
|
404 |
- if (4 < $numArgs = \func_num_args()) { |
|
405 |
- $context = $scope ? (func_get_arg(4) ?: []) : []; |
|
406 |
- } else { |
|
407 |
- $context = []; |
|
408 |
- } |
|
409 |
- |
|
410 |
- if (isset($context['GLOBALS']) && $scope) { |
|
411 |
- $e = $context; // Whatever the signature of the method, |
|
412 |
- unset($e['GLOBALS'], $context); // $context is always a reference in 5.3 |
|
413 |
- $context = $e; |
|
414 |
- } |
|
415 |
- |
|
416 |
- if (false !== strpos($message, "class@anonymous\0")) { |
|
417 |
- $logMessage = $this->levels[$type].': '.(new FlattenException())->setMessage($message)->getMessage(); |
|
418 |
- } else { |
|
419 |
- $logMessage = $this->levels[$type].': '.$message; |
|
420 |
- } |
|
421 |
- |
|
422 |
- if (null !== self::$toStringException) { |
|
423 |
- $errorAsException = self::$toStringException; |
|
424 |
- self::$toStringException = null; |
|
425 |
- } elseif (!$throw && !($type & $level)) { |
|
426 |
- if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) { |
|
427 |
- $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : []; |
|
428 |
- $errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace); |
|
429 |
- } elseif (isset(self::$silencedErrorCache[$id][$message])) { |
|
430 |
- $lightTrace = null; |
|
431 |
- $errorAsException = self::$silencedErrorCache[$id][$message]; |
|
432 |
- ++$errorAsException->count; |
|
433 |
- } else { |
|
434 |
- $lightTrace = []; |
|
435 |
- $errorAsException = null; |
|
436 |
- } |
|
437 |
- |
|
438 |
- if (100 < ++self::$silencedErrorCount) { |
|
439 |
- self::$silencedErrorCache = $lightTrace = []; |
|
440 |
- self::$silencedErrorCount = 1; |
|
441 |
- } |
|
442 |
- if ($errorAsException) { |
|
443 |
- self::$silencedErrorCache[$id][$message] = $errorAsException; |
|
444 |
- } |
|
445 |
- if (null === $lightTrace) { |
|
446 |
- return; |
|
447 |
- } |
|
448 |
- } else { |
|
449 |
- $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); |
|
450 |
- |
|
451 |
- if ($throw || $this->tracedErrors & $type) { |
|
452 |
- $backtrace = $errorAsException->getTrace(); |
|
453 |
- $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); |
|
454 |
- $this->traceReflector->setValue($errorAsException, $lightTrace); |
|
455 |
- } else { |
|
456 |
- $this->traceReflector->setValue($errorAsException, []); |
|
457 |
- $backtrace = []; |
|
458 |
- } |
|
459 |
- } |
|
460 |
- |
|
461 |
- if ($throw) { |
|
462 |
- if (E_USER_ERROR & $type) { |
|
463 |
- for ($i = 1; isset($backtrace[$i]); ++$i) { |
|
464 |
- if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) |
|
465 |
- && '__toString' === $backtrace[$i]['function'] |
|
466 |
- && '->' === $backtrace[$i]['type'] |
|
467 |
- && !isset($backtrace[$i - 1]['class']) |
|
468 |
- && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) |
|
469 |
- ) { |
|
470 |
- // Here, we know trigger_error() has been called from __toString(). |
|
471 |
- // PHP triggers a fatal error when throwing from __toString(). |
|
472 |
- // A small convention allows working around the limitation: |
|
473 |
- // given a caught $e exception in __toString(), quitting the method with |
|
474 |
- // `return trigger_error($e, E_USER_ERROR);` allows this error handler |
|
475 |
- // to make $e get through the __toString() barrier. |
|
476 |
- |
|
477 |
- foreach ($context as $e) { |
|
478 |
- if ($e instanceof \Throwable && $e->__toString() === $message) { |
|
479 |
- self::$toStringException = $e; |
|
480 |
- |
|
481 |
- return true; |
|
482 |
- } |
|
483 |
- } |
|
484 |
- |
|
485 |
- // Display the original error message instead of the default one. |
|
486 |
- $this->handleException($errorAsException); |
|
487 |
- |
|
488 |
- // Stop the process by giving back the error to the native handler. |
|
489 |
- return false; |
|
490 |
- } |
|
491 |
- } |
|
492 |
- } |
|
493 |
- |
|
494 |
- throw $errorAsException; |
|
495 |
- } |
|
496 |
- |
|
497 |
- if ($this->isRecursive) { |
|
498 |
- $log = 0; |
|
499 |
- } else { |
|
500 |
- if (!\defined('HHVM_VERSION')) { |
|
501 |
- $currentErrorHandler = set_error_handler('var_dump'); |
|
502 |
- restore_error_handler(); |
|
503 |
- } |
|
504 |
- |
|
505 |
- try { |
|
506 |
- $this->isRecursive = true; |
|
507 |
- $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; |
|
508 |
- $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []); |
|
509 |
- } finally { |
|
510 |
- $this->isRecursive = false; |
|
511 |
- |
|
512 |
- if (!\defined('HHVM_VERSION')) { |
|
513 |
- set_error_handler($currentErrorHandler); |
|
514 |
- } |
|
515 |
- } |
|
516 |
- } |
|
517 |
- |
|
518 |
- return !$silenced && $type && $log; |
|
519 |
- } |
|
520 |
- |
|
521 |
- /** |
|
522 |
- * Handles an exception by logging then forwarding it to another handler. |
|
523 |
- * |
|
524 |
- * @param \Exception|\Throwable $exception An exception to handle |
|
525 |
- * @param array $error An array as returned by error_get_last() |
|
526 |
- * |
|
527 |
- * @internal |
|
528 |
- */ |
|
529 |
- public function handleException($exception, array $error = null) |
|
530 |
- { |
|
531 |
- if (null === $error) { |
|
532 |
- self::$exitCode = 255; |
|
533 |
- } |
|
534 |
- if (!$exception instanceof \Exception) { |
|
535 |
- $exception = new FatalThrowableError($exception); |
|
536 |
- } |
|
537 |
- $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; |
|
538 |
- $handlerException = null; |
|
539 |
- |
|
540 |
- if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { |
|
541 |
- if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) { |
|
542 |
- $message = (new FlattenException())->setMessage($message)->getMessage(); |
|
543 |
- } |
|
544 |
- if ($exception instanceof FatalErrorException) { |
|
545 |
- if ($exception instanceof FatalThrowableError) { |
|
546 |
- $error = [ |
|
547 |
- 'type' => $type, |
|
548 |
- 'message' => $message, |
|
549 |
- 'file' => $exception->getFile(), |
|
550 |
- 'line' => $exception->getLine(), |
|
551 |
- ]; |
|
552 |
- } else { |
|
553 |
- $message = 'Fatal '.$message; |
|
554 |
- } |
|
555 |
- } elseif ($exception instanceof \ErrorException) { |
|
556 |
- $message = 'Uncaught '.$message; |
|
557 |
- } else { |
|
558 |
- $message = 'Uncaught Exception: '.$message; |
|
559 |
- } |
|
560 |
- } |
|
561 |
- if ($this->loggedErrors & $type) { |
|
562 |
- try { |
|
563 |
- $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]); |
|
564 |
- } catch (\Throwable $handlerException) { |
|
565 |
- } |
|
566 |
- } |
|
567 |
- if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { |
|
568 |
- foreach ($this->getFatalErrorHandlers() as $handler) { |
|
569 |
- if ($e = $handler->handleError($error, $exception)) { |
|
570 |
- $exception = $e; |
|
571 |
- break; |
|
572 |
- } |
|
573 |
- } |
|
574 |
- } |
|
575 |
- $exceptionHandler = $this->exceptionHandler; |
|
576 |
- $this->exceptionHandler = null; |
|
577 |
- try { |
|
578 |
- if (null !== $exceptionHandler) { |
|
579 |
- return $exceptionHandler($exception); |
|
580 |
- } |
|
581 |
- $handlerException = $handlerException ?: $exception; |
|
582 |
- } catch (\Throwable $handlerException) { |
|
583 |
- } |
|
584 |
- if ($exception === $handlerException) { |
|
585 |
- self::$reservedMemory = null; // Disable the fatal error handler |
|
586 |
- throw $exception; // Give back $exception to the native handler |
|
587 |
- } |
|
588 |
- $this->handleException($handlerException); |
|
589 |
- } |
|
590 |
- |
|
591 |
- /** |
|
592 |
- * Shutdown registered function for handling PHP fatal errors. |
|
593 |
- * |
|
594 |
- * @param array $error An array as returned by error_get_last() |
|
595 |
- * |
|
596 |
- * @internal |
|
597 |
- */ |
|
598 |
- public static function handleFatalError(array $error = null) |
|
599 |
- { |
|
600 |
- if (null === self::$reservedMemory) { |
|
601 |
- return; |
|
602 |
- } |
|
603 |
- |
|
604 |
- $handler = self::$reservedMemory = null; |
|
605 |
- $handlers = []; |
|
606 |
- $previousHandler = null; |
|
607 |
- $sameHandlerLimit = 10; |
|
608 |
- |
|
609 |
- while (!\is_array($handler) || !$handler[0] instanceof self) { |
|
610 |
- $handler = set_exception_handler('var_dump'); |
|
611 |
- restore_exception_handler(); |
|
612 |
- |
|
613 |
- if (!$handler) { |
|
614 |
- break; |
|
615 |
- } |
|
616 |
- restore_exception_handler(); |
|
617 |
- |
|
618 |
- if ($handler !== $previousHandler) { |
|
619 |
- array_unshift($handlers, $handler); |
|
620 |
- $previousHandler = $handler; |
|
621 |
- } elseif (0 === --$sameHandlerLimit) { |
|
622 |
- $handler = null; |
|
623 |
- break; |
|
624 |
- } |
|
625 |
- } |
|
626 |
- foreach ($handlers as $h) { |
|
627 |
- set_exception_handler($h); |
|
628 |
- } |
|
629 |
- if (!$handler) { |
|
630 |
- return; |
|
631 |
- } |
|
632 |
- if ($handler !== $h) { |
|
633 |
- $handler[0]->setExceptionHandler($h); |
|
634 |
- } |
|
635 |
- $handler = $handler[0]; |
|
636 |
- $handlers = []; |
|
637 |
- |
|
638 |
- if ($exit = null === $error) { |
|
639 |
- $error = error_get_last(); |
|
640 |
- } |
|
641 |
- |
|
642 |
- if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { |
|
643 |
- // Let's not throw anymore but keep logging |
|
644 |
- $handler->throwAt(0, true); |
|
645 |
- $trace = isset($error['backtrace']) ? $error['backtrace'] : null; |
|
646 |
- |
|
647 |
- if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { |
|
648 |
- $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); |
|
649 |
- } else { |
|
650 |
- $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); |
|
651 |
- } |
|
652 |
- } else { |
|
653 |
- $exception = null; |
|
654 |
- } |
|
655 |
- |
|
656 |
- try { |
|
657 |
- if (null !== $exception) { |
|
658 |
- self::$exitCode = 255; |
|
659 |
- $handler->handleException($exception, $error); |
|
660 |
- } |
|
661 |
- } catch (FatalErrorException $e) { |
|
662 |
- // Ignore this re-throw |
|
663 |
- } |
|
664 |
- |
|
665 |
- if ($exit && self::$exitCode) { |
|
666 |
- $exitCode = self::$exitCode; |
|
667 |
- register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); |
|
668 |
- } |
|
669 |
- } |
|
670 |
- |
|
671 |
- /** |
|
672 |
- * Gets the fatal error handlers. |
|
673 |
- * |
|
674 |
- * Override this method if you want to define more fatal error handlers. |
|
675 |
- * |
|
676 |
- * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface |
|
677 |
- */ |
|
678 |
- protected function getFatalErrorHandlers() |
|
679 |
- { |
|
680 |
- return [ |
|
681 |
- new UndefinedFunctionFatalErrorHandler(), |
|
682 |
- new UndefinedMethodFatalErrorHandler(), |
|
683 |
- new ClassNotFoundFatalErrorHandler(), |
|
684 |
- ]; |
|
685 |
- } |
|
686 |
- |
|
687 |
- /** |
|
688 |
- * Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader. |
|
689 |
- */ |
|
690 |
- private function cleanTrace($backtrace, $type, $file, $line, $throw) |
|
691 |
- { |
|
692 |
- $lightTrace = $backtrace; |
|
693 |
- |
|
694 |
- for ($i = 0; isset($backtrace[$i]); ++$i) { |
|
695 |
- if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { |
|
696 |
- $lightTrace = \array_slice($lightTrace, 1 + $i); |
|
697 |
- break; |
|
698 |
- } |
|
699 |
- } |
|
700 |
- if (class_exists(DebugClassLoader::class, false)) { |
|
701 |
- for ($i = \count($lightTrace) - 2; 0 < $i; --$i) { |
|
702 |
- if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) { |
|
703 |
- array_splice($lightTrace, --$i, 2); |
|
704 |
- } |
|
705 |
- } |
|
706 |
- } |
|
707 |
- if (!($throw || $this->scopedErrors & $type)) { |
|
708 |
- for ($i = 0; isset($lightTrace[$i]); ++$i) { |
|
709 |
- unset($lightTrace[$i]['args'], $lightTrace[$i]['object']); |
|
710 |
- } |
|
711 |
- } |
|
712 |
- |
|
713 |
- return $lightTrace; |
|
714 |
- } |
|
715 |
-} |
1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,715 @@ |
1 |
+<?php |
|
2 |
+ |
|
3 |
+/* |
|
4 |
+ * This file is part of the Symfony package. |
|
5 |
+ * |
|
6 |
+ * (c) Fabien Potencier <fabien@symfony.com> |
|
7 |
+ * |
|
8 |
+ * For the full copyright and license information, please view the LICENSE |
|
9 |
+ * file that was distributed with this source code. |
|
10 |
+ */ |
|
11 |
+ |
|
12 |
+namespace Symfony\Component\Debug; |
|
13 |
+ |
|
14 |
+use Psr\Log\LoggerInterface; |
|
15 |
+use Psr\Log\LogLevel; |
|
16 |
+use Symfony\Component\Debug\Exception\FatalErrorException; |
|
17 |
+use Symfony\Component\Debug\Exception\FatalThrowableError; |
|
18 |
+use Symfony\Component\Debug\Exception\FlattenException; |
|
19 |
+use Symfony\Component\Debug\Exception\OutOfMemoryException; |
|
20 |
+use Symfony\Component\Debug\Exception\SilencedErrorContext; |
|
21 |
+use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler; |
|
22 |
+use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface; |
|
23 |
+use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler; |
|
24 |
+use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler; |
|
25 |
+ |
|
26 |
+/** |
|
27 |
+ * A generic ErrorHandler for the PHP engine. |
|
28 |
+ * |
|
29 |
+ * Provides five bit fields that control how errors are handled: |
|
30 |
+ * - thrownErrors: errors thrown as \ErrorException |
|
31 |
+ * - loggedErrors: logged errors, when not @-silenced |
|
32 |
+ * - scopedErrors: errors thrown or logged with their local context |
|
33 |
+ * - tracedErrors: errors logged with their stack trace |
|
34 |
+ * - screamedErrors: never @-silenced errors |
|
35 |
+ * |
|
36 |
+ * Each error level can be logged by a dedicated PSR-3 logger object. |
|
37 |
+ * Screaming only applies to logging. |
|
38 |
+ * Throwing takes precedence over logging. |
|
39 |
+ * Uncaught exceptions are logged as E_ERROR. |
|
40 |
+ * E_DEPRECATED and E_USER_DEPRECATED levels never throw. |
|
41 |
+ * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw. |
|
42 |
+ * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so. |
|
43 |
+ * As errors have a performance cost, repeated errors are all logged, so that the developer |
|
44 |
+ * can see them and weight them as more important to fix than others of the same level. |
|
45 |
+ * |
|
46 |
+ * @author Nicolas Grekas <p@tchwork.com> |
|
47 |
+ * @author Grégoire Pineau <lyrixx@lyrixx.info> |
|
48 |
+ * |
|
49 |
+ * @final since Symfony 4.3 |
|
50 |
+ */ |
|
51 |
+class ErrorHandler |
|
52 |
+{ |
|
53 |
+ private $levels = [ |
|
54 |
+ E_DEPRECATED => 'Deprecated', |
|
55 |
+ E_USER_DEPRECATED => 'User Deprecated', |
|
56 |
+ E_NOTICE => 'Notice', |
|
57 |
+ E_USER_NOTICE => 'User Notice', |
|
58 |
+ E_STRICT => 'Runtime Notice', |
|
59 |
+ E_WARNING => 'Warning', |
|
60 |
+ E_USER_WARNING => 'User Warning', |
|
61 |
+ E_COMPILE_WARNING => 'Compile Warning', |
|
62 |
+ E_CORE_WARNING => 'Core Warning', |
|
63 |
+ E_USER_ERROR => 'User Error', |
|
64 |
+ E_RECOVERABLE_ERROR => 'Catchable Fatal Error', |
|
65 |
+ E_COMPILE_ERROR => 'Compile Error', |
|
66 |
+ E_PARSE => 'Parse Error', |
|
67 |
+ E_ERROR => 'Error', |
|
68 |
+ E_CORE_ERROR => 'Core Error', |
|
69 |
+ ]; |
|
70 |
+ |
|
71 |
+ private $loggers = [ |
|
72 |
+ E_DEPRECATED => [null, LogLevel::INFO], |
|
73 |
+ E_USER_DEPRECATED => [null, LogLevel::INFO], |
|
74 |
+ E_NOTICE => [null, LogLevel::WARNING], |
|
75 |
+ E_USER_NOTICE => [null, LogLevel::WARNING], |
|
76 |
+ E_STRICT => [null, LogLevel::WARNING], |
|
77 |
+ E_WARNING => [null, LogLevel::WARNING], |
|
78 |
+ E_USER_WARNING => [null, LogLevel::WARNING], |
|
79 |
+ E_COMPILE_WARNING => [null, LogLevel::WARNING], |
|
80 |
+ E_CORE_WARNING => [null, LogLevel::WARNING], |
|
81 |
+ E_USER_ERROR => [null, LogLevel::CRITICAL], |
|
82 |
+ E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL], |
|
83 |
+ E_COMPILE_ERROR => [null, LogLevel::CRITICAL], |
|
84 |
+ E_PARSE => [null, LogLevel::CRITICAL], |
|
85 |
+ E_ERROR => [null, LogLevel::CRITICAL], |
|
86 |
+ E_CORE_ERROR => [null, LogLevel::CRITICAL], |
|
87 |
+ ]; |
|
88 |
+ |
|
89 |
+ private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED |
|
90 |
+ private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED |
|
91 |
+ private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE |
|
92 |
+ private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE |
|
93 |
+ private $loggedErrors = 0; |
|
94 |
+ private $traceReflector; |
|
95 |
+ |
|
96 |
+ private $isRecursive = 0; |
|
97 |
+ private $isRoot = false; |
|
98 |
+ private $exceptionHandler; |
|
99 |
+ private $bootstrappingLogger; |
|
100 |
+ |
|
101 |
+ private static $reservedMemory; |
|
102 |
+ private static $toStringException = null; |
|
103 |
+ private static $silencedErrorCache = []; |
|
104 |
+ private static $silencedErrorCount = 0; |
|
105 |
+ private static $exitCode = 0; |
|
106 |
+ |
|
107 |
+ /** |
|
108 |
+ * Registers the error handler. |
|
109 |
+ * |
|
110 |
+ * @param self|null $handler The handler to register |
|
111 |
+ * @param bool $replace Whether to replace or not any existing handler |
|
112 |
+ * |
|
113 |
+ * @return self The registered error handler |
|
114 |
+ */ |
|
115 |
+ public static function register(self $handler = null, $replace = true) |
|
116 |
+ { |
|
117 |
+ if (null === self::$reservedMemory) { |
|
118 |
+ self::$reservedMemory = str_repeat('x', 10240); |
|
119 |
+ register_shutdown_function(__CLASS__.'::handleFatalError'); |
|
120 |
+ } |
|
121 |
+ |
|
122 |
+ if ($handlerIsNew = null === $handler) { |
|
123 |
+ $handler = new static(); |
|
124 |
+ } |
|
125 |
+ |
|
126 |
+ if (null === $prev = set_error_handler([$handler, 'handleError'])) { |
|
127 |
+ restore_error_handler(); |
|
128 |
+ // Specifying the error types earlier would expose us to https://bugs.php.net/63206 |
|
129 |
+ set_error_handler([$handler, 'handleError'], $handler->thrownErrors | $handler->loggedErrors); |
|
130 |
+ $handler->isRoot = true; |
|
131 |
+ } |
|
132 |
+ |
|
133 |
+ if ($handlerIsNew && \is_array($prev) && $prev[0] instanceof self) { |
|
134 |
+ $handler = $prev[0]; |
|
135 |
+ $replace = false; |
|
136 |
+ } |
|
137 |
+ if (!$replace && $prev) { |
|
138 |
+ restore_error_handler(); |
|
139 |
+ $handlerIsRegistered = \is_array($prev) && $handler === $prev[0]; |
|
140 |
+ } else { |
|
141 |
+ $handlerIsRegistered = true; |
|
142 |
+ } |
|
143 |
+ if (\is_array($prev = set_exception_handler([$handler, 'handleException'])) && $prev[0] instanceof self) { |
|
144 |
+ restore_exception_handler(); |
|
145 |
+ if (!$handlerIsRegistered) { |
|
146 |
+ $handler = $prev[0]; |
|
147 |
+ } elseif ($handler !== $prev[0] && $replace) { |
|
148 |
+ set_exception_handler([$handler, 'handleException']); |
|
149 |
+ $p = $prev[0]->setExceptionHandler(null); |
|
150 |
+ $handler->setExceptionHandler($p); |
|
151 |
+ $prev[0]->setExceptionHandler($p); |
|
152 |
+ } |
|
153 |
+ } else { |
|
154 |
+ $handler->setExceptionHandler($prev); |
|
155 |
+ } |
|
156 |
+ |
|
157 |
+ $handler->throwAt(E_ALL & $handler->thrownErrors, true); |
|
158 |
+ |
|
159 |
+ return $handler; |
|
160 |
+ } |
|
161 |
+ |
|
162 |
+ public function __construct(BufferingLogger $bootstrappingLogger = null) |
|
163 |
+ { |
|
164 |
+ if ($bootstrappingLogger) { |
|
165 |
+ $this->bootstrappingLogger = $bootstrappingLogger; |
|
166 |
+ $this->setDefaultLogger($bootstrappingLogger); |
|
167 |
+ } |
|
168 |
+ $this->traceReflector = new \ReflectionProperty('Exception', 'trace'); |
|
169 |
+ $this->traceReflector->setAccessible(true); |
|
170 |
+ } |
|
171 |
+ |
|
172 |
+ /** |
|
173 |
+ * Sets a logger to non assigned errors levels. |
|
174 |
+ * |
|
175 |
+ * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels |
|
176 |
+ * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants |
|
177 |
+ * @param bool $replace Whether to replace or not any existing logger |
|
178 |
+ */ |
|
179 |
+ public function setDefaultLogger(LoggerInterface $logger, $levels = E_ALL, $replace = false) |
|
180 |
+ { |
|
181 |
+ $loggers = []; |
|
182 |
+ |
|
183 |
+ if (\is_array($levels)) { |
|
184 |
+ foreach ($levels as $type => $logLevel) { |
|
185 |
+ if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) { |
|
186 |
+ $loggers[$type] = [$logger, $logLevel]; |
|
187 |
+ } |
|
188 |
+ } |
|
189 |
+ } else { |
|
190 |
+ if (null === $levels) { |
|
191 |
+ $levels = E_ALL; |
|
192 |
+ } |
|
193 |
+ foreach ($this->loggers as $type => $log) { |
|
194 |
+ if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) { |
|
195 |
+ $log[0] = $logger; |
|
196 |
+ $loggers[$type] = $log; |
|
197 |
+ } |
|
198 |
+ } |
|
199 |
+ } |
|
200 |
+ |
|
201 |
+ $this->setLoggers($loggers); |
|
202 |
+ } |
|
203 |
+ |
|
204 |
+ /** |
|
205 |
+ * Sets a logger for each error level. |
|
206 |
+ * |
|
207 |
+ * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map |
|
208 |
+ * |
|
209 |
+ * @return array The previous map |
|
210 |
+ * |
|
211 |
+ * @throws \InvalidArgumentException |
|
212 |
+ */ |
|
213 |
+ public function setLoggers(array $loggers) |
|
214 |
+ { |
|
215 |
+ $prevLogged = $this->loggedErrors; |
|
216 |
+ $prev = $this->loggers; |
|
217 |
+ $flush = []; |
|
218 |
+ |
|
219 |
+ foreach ($loggers as $type => $log) { |
|
220 |
+ if (!isset($prev[$type])) { |
|
221 |
+ throw new \InvalidArgumentException('Unknown error type: '.$type); |
|
222 |
+ } |
|
223 |
+ if (!\is_array($log)) { |
|
224 |
+ $log = [$log]; |
|
225 |
+ } elseif (!\array_key_exists(0, $log)) { |
|
226 |
+ throw new \InvalidArgumentException('No logger provided'); |
|
227 |
+ } |
|
228 |
+ if (null === $log[0]) { |
|
229 |
+ $this->loggedErrors &= ~$type; |
|
230 |
+ } elseif ($log[0] instanceof LoggerInterface) { |
|
231 |
+ $this->loggedErrors |= $type; |
|
232 |
+ } else { |
|
233 |
+ throw new \InvalidArgumentException('Invalid logger provided'); |
|
234 |
+ } |
|
235 |
+ $this->loggers[$type] = $log + $prev[$type]; |
|
236 |
+ |
|
237 |
+ if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) { |
|
238 |
+ $flush[$type] = $type; |
|
239 |
+ } |
|
240 |
+ } |
|
241 |
+ $this->reRegister($prevLogged | $this->thrownErrors); |
|
242 |
+ |
|
243 |
+ if ($flush) { |
|
244 |
+ foreach ($this->bootstrappingLogger->cleanLogs() as $log) { |
|
245 |
+ $type = $log[2]['exception'] instanceof \ErrorException ? $log[2]['exception']->getSeverity() : E_ERROR; |
|
246 |
+ if (!isset($flush[$type])) { |
|
247 |
+ $this->bootstrappingLogger->log($log[0], $log[1], $log[2]); |
|
248 |
+ } elseif ($this->loggers[$type][0]) { |
|
249 |
+ $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]); |
|
250 |
+ } |
|
251 |
+ } |
|
252 |
+ } |
|
253 |
+ |
|
254 |
+ return $prev; |
|
255 |
+ } |
|
256 |
+ |
|
257 |
+ /** |
|
258 |
+ * Sets a user exception handler. |
|
259 |
+ * |
|
260 |
+ * @param callable $handler A handler that will be called on Exception |
|
261 |
+ * |
|
262 |
+ * @return callable|null The previous exception handler |
|
263 |
+ */ |
|
264 |
+ public function setExceptionHandler(callable $handler = null) |
|
265 |
+ { |
|
266 |
+ $prev = $this->exceptionHandler; |
|
267 |
+ $this->exceptionHandler = $handler; |
|
268 |
+ |
|
269 |
+ return $prev; |
|
270 |
+ } |
|
271 |
+ |
|
272 |
+ /** |
|
273 |
+ * Sets the PHP error levels that throw an exception when a PHP error occurs. |
|
274 |
+ * |
|
275 |
+ * @param int $levels A bit field of E_* constants for thrown errors |
|
276 |
+ * @param bool $replace Replace or amend the previous value |
|
277 |
+ * |
|
278 |
+ * @return int The previous value |
|
279 |
+ */ |
|
280 |
+ public function throwAt($levels, $replace = false) |
|
281 |
+ { |
|
282 |
+ $prev = $this->thrownErrors; |
|
283 |
+ $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED; |
|
284 |
+ if (!$replace) { |
|
285 |
+ $this->thrownErrors |= $prev; |
|
286 |
+ } |
|
287 |
+ $this->reRegister($prev | $this->loggedErrors); |
|
288 |
+ |
|
289 |
+ return $prev; |
|
290 |
+ } |
|
291 |
+ |
|
292 |
+ /** |
|
293 |
+ * Sets the PHP error levels for which local variables are preserved. |
|
294 |
+ * |
|
295 |
+ * @param int $levels A bit field of E_* constants for scoped errors |
|
296 |
+ * @param bool $replace Replace or amend the previous value |
|
297 |
+ * |
|
298 |
+ * @return int The previous value |
|
299 |
+ */ |
|
300 |
+ public function scopeAt($levels, $replace = false) |
|
301 |
+ { |
|
302 |
+ $prev = $this->scopedErrors; |
|
303 |
+ $this->scopedErrors = (int) $levels; |
|
304 |
+ if (!$replace) { |
|
305 |
+ $this->scopedErrors |= $prev; |
|
306 |
+ } |
|
307 |
+ |
|
308 |
+ return $prev; |
|
309 |
+ } |
|
310 |
+ |
|
311 |
+ /** |
|
312 |
+ * Sets the PHP error levels for which the stack trace is preserved. |
|
313 |
+ * |
|
314 |
+ * @param int $levels A bit field of E_* constants for traced errors |
|
315 |
+ * @param bool $replace Replace or amend the previous value |
|
316 |
+ * |
|
317 |
+ * @return int The previous value |
|
318 |
+ */ |
|
319 |
+ public function traceAt($levels, $replace = false) |
|
320 |
+ { |
|
321 |
+ $prev = $this->tracedErrors; |
|
322 |
+ $this->tracedErrors = (int) $levels; |
|
323 |
+ if (!$replace) { |
|
324 |
+ $this->tracedErrors |= $prev; |
|
325 |
+ } |
|
326 |
+ |
|
327 |
+ return $prev; |
|
328 |
+ } |
|
329 |
+ |
|
330 |
+ /** |
|
331 |
+ * Sets the error levels where the @-operator is ignored. |
|
332 |
+ * |
|
333 |
+ * @param int $levels A bit field of E_* constants for screamed errors |
|
334 |
+ * @param bool $replace Replace or amend the previous value |
|
335 |
+ * |
|
336 |
+ * @return int The previous value |
|
337 |
+ */ |
|
338 |
+ public function screamAt($levels, $replace = false) |
|
339 |
+ { |
|
340 |
+ $prev = $this->screamedErrors; |
|
341 |
+ $this->screamedErrors = (int) $levels; |
|
342 |
+ if (!$replace) { |
|
343 |
+ $this->screamedErrors |= $prev; |
|
344 |
+ } |
|
345 |
+ |
|
346 |
+ return $prev; |
|
347 |
+ } |
|
348 |
+ |
|
349 |
+ /** |
|
350 |
+ * Re-registers as a PHP error handler if levels changed. |
|
351 |
+ */ |
|
352 |
+ private function reRegister($prev) |
|
353 |
+ { |
|
354 |
+ if ($prev !== $this->thrownErrors | $this->loggedErrors) { |
|
355 |
+ $handler = set_error_handler('var_dump'); |
|
356 |
+ $handler = \is_array($handler) ? $handler[0] : null; |
|
357 |
+ restore_error_handler(); |
|
358 |
+ if ($handler === $this) { |
|
359 |
+ restore_error_handler(); |
|
360 |
+ if ($this->isRoot) { |
|
361 |
+ set_error_handler([$this, 'handleError'], $this->thrownErrors | $this->loggedErrors); |
|
362 |
+ } else { |
|
363 |
+ set_error_handler([$this, 'handleError']); |
|
364 |
+ } |
|
365 |
+ } |
|
366 |
+ } |
|
367 |
+ } |
|
368 |
+ |
|
369 |
+ /** |
|
370 |
+ * Handles errors by filtering then logging them according to the configured bit fields. |
|
371 |
+ * |
|
372 |
+ * @param int $type One of the E_* constants |
|
373 |
+ * @param string $message |
|
374 |
+ * @param string $file |
|
375 |
+ * @param int $line |
|
376 |
+ * |
|
377 |
+ * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself |
|
378 |
+ * |
|
379 |
+ * @throws \ErrorException When $this->thrownErrors requests so |
|
380 |
+ * |
|
381 |
+ * @internal |
|
382 |
+ */ |
|
383 |
+ public function handleError($type, $message, $file, $line) |
|
384 |
+ { |
|
385 |
+ // @deprecated to be removed in Symfony 5.0 |
|
386 |
+ if (\PHP_VERSION_ID >= 70300 && $message && '"' === $message[0] && 0 === strpos($message, '"continue') && preg_match('/^"continue(?: \d++)?" targeting switch is equivalent to "break(?: \d++)?"\. Did you mean to use "continue(?: \d++)?"\?$/', $message)) { |
|
387 |
+ $type = E_DEPRECATED; |
|
388 |
+ } |
|
389 |
+ |
|
390 |
+ // Level is the current error reporting level to manage silent error. |
|
391 |
+ $level = error_reporting(); |
|
392 |
+ $silenced = 0 === ($level & $type); |
|
393 |
+ // Strong errors are not authorized to be silenced. |
|
394 |
+ $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED; |
|
395 |
+ $log = $this->loggedErrors & $type; |
|
396 |
+ $throw = $this->thrownErrors & $type & $level; |
|
397 |
+ $type &= $level | $this->screamedErrors; |
|
398 |
+ |
|
399 |
+ if (!$type || (!$log && !$throw)) { |
|
400 |
+ return !$silenced && $type && $log; |
|
401 |
+ } |
|
402 |
+ $scope = $this->scopedErrors & $type; |
|
403 |
+ |
|
404 |
+ if (4 < $numArgs = \func_num_args()) { |
|
405 |
+ $context = $scope ? (func_get_arg(4) ?: []) : []; |
|
406 |
+ } else { |
|
407 |
+ $context = []; |
|
408 |
+ } |
|
409 |
+ |
|
410 |
+ if (isset($context['GLOBALS']) && $scope) { |
|
411 |
+ $e = $context; // Whatever the signature of the method, |
|
412 |
+ unset($e['GLOBALS'], $context); // $context is always a reference in 5.3 |
|
413 |
+ $context = $e; |
|
414 |
+ } |
|
415 |
+ |
|
416 |
+ if (false !== strpos($message, "class@anonymous\0")) { |
|
417 |
+ $logMessage = $this->levels[$type].': '.(new FlattenException())->setMessage($message)->getMessage(); |
|
418 |
+ } else { |
|
419 |
+ $logMessage = $this->levels[$type].': '.$message; |
|
420 |
+ } |
|
421 |
+ |
|
422 |
+ if (null !== self::$toStringException) { |
|
423 |
+ $errorAsException = self::$toStringException; |
|
424 |
+ self::$toStringException = null; |
|
425 |
+ } elseif (!$throw && !($type & $level)) { |
|
426 |
+ if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) { |
|
427 |
+ $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5), $type, $file, $line, false) : []; |
|
428 |
+ $errorAsException = new SilencedErrorContext($type, $file, $line, isset($lightTrace[1]) ? [$lightTrace[0]] : $lightTrace); |
|
429 |
+ } elseif (isset(self::$silencedErrorCache[$id][$message])) { |
|
430 |
+ $lightTrace = null; |
|
431 |
+ $errorAsException = self::$silencedErrorCache[$id][$message]; |
|
432 |
+ ++$errorAsException->count; |
|
433 |
+ } else { |
|
434 |
+ $lightTrace = []; |
|
435 |
+ $errorAsException = null; |
|
436 |
+ } |
|
437 |
+ |
|
438 |
+ if (100 < ++self::$silencedErrorCount) { |
|
439 |
+ self::$silencedErrorCache = $lightTrace = []; |
|
440 |
+ self::$silencedErrorCount = 1; |
|
441 |
+ } |
|
442 |
+ if ($errorAsException) { |
|
443 |
+ self::$silencedErrorCache[$id][$message] = $errorAsException; |
|
444 |
+ } |
|
445 |
+ if (null === $lightTrace) { |
|
446 |
+ return; |
|
447 |
+ } |
|
448 |
+ } else { |
|
449 |
+ $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line); |
|
450 |
+ |
|
451 |
+ if ($throw || $this->tracedErrors & $type) { |
|
452 |
+ $backtrace = $errorAsException->getTrace(); |
|
453 |
+ $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw); |
|
454 |
+ $this->traceReflector->setValue($errorAsException, $lightTrace); |
|
455 |
+ } else { |
|
456 |
+ $this->traceReflector->setValue($errorAsException, []); |
|
457 |
+ $backtrace = []; |
|
458 |
+ } |
|
459 |
+ } |
|
460 |
+ |
|
461 |
+ if ($throw) { |
|
462 |
+ if (E_USER_ERROR & $type) { |
|
463 |
+ for ($i = 1; isset($backtrace[$i]); ++$i) { |
|
464 |
+ if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function']) |
|
465 |
+ && '__toString' === $backtrace[$i]['function'] |
|
466 |
+ && '->' === $backtrace[$i]['type'] |
|
467 |
+ && !isset($backtrace[$i - 1]['class']) |
|
468 |
+ && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function']) |
|
469 |
+ ) { |
|
470 |
+ // Here, we know trigger_error() has been called from __toString(). |
|
471 |
+ // PHP triggers a fatal error when throwing from __toString(). |
|
472 |
+ // A small convention allows working around the limitation: |
|
473 |
+ // given a caught $e exception in __toString(), quitting the method with |
|
474 |
+ // `return trigger_error($e, E_USER_ERROR);` allows this error handler |
|
475 |
+ // to make $e get through the __toString() barrier. |
|
476 |
+ |
|
477 |
+ foreach ($context as $e) { |
|
478 |
+ if ($e instanceof \Throwable && $e->__toString() === $message) { |
|
479 |
+ self::$toStringException = $e; |
|
480 |
+ |
|
481 |
+ return true; |
|
482 |
+ } |
|
483 |
+ } |
|
484 |
+ |
|
485 |
+ // Display the original error message instead of the default one. |
|
486 |
+ $this->handleException($errorAsException); |
|
487 |
+ |
|
488 |
+ // Stop the process by giving back the error to the native handler. |
|
489 |
+ return false; |
|
490 |
+ } |
|
491 |
+ } |
|
492 |
+ } |
|
493 |
+ |
|
494 |
+ throw $errorAsException; |
|
495 |
+ } |
|
496 |
+ |
|
497 |
+ if ($this->isRecursive) { |
|
498 |
+ $log = 0; |
|
499 |
+ } else { |
|
500 |
+ if (!\defined('HHVM_VERSION')) { |
|
501 |
+ $currentErrorHandler = set_error_handler('var_dump'); |
|
502 |
+ restore_error_handler(); |
|
503 |
+ } |
|
504 |
+ |
|
505 |
+ try { |
|
506 |
+ $this->isRecursive = true; |
|
507 |
+ $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG; |
|
508 |
+ $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []); |
|
509 |
+ } finally { |
|
510 |
+ $this->isRecursive = false; |
|
511 |
+ |
|
512 |
+ if (!\defined('HHVM_VERSION')) { |
|
513 |
+ set_error_handler($currentErrorHandler); |
|
514 |
+ } |
|
515 |
+ } |
|
516 |
+ } |
|
517 |
+ |
|
518 |
+ return !$silenced && $type && $log; |
|
519 |
+ } |
|
520 |
+ |
|
521 |
+ /** |
|
522 |
+ * Handles an exception by logging then forwarding it to another handler. |
|
523 |
+ * |
|
524 |
+ * @param \Exception|\Throwable $exception An exception to handle |
|
525 |
+ * @param array $error An array as returned by error_get_last() |
|
526 |
+ * |
|
527 |
+ * @internal |
|
528 |
+ */ |
|
529 |
+ public function handleException($exception, array $error = null) |
|
530 |
+ { |
|
531 |
+ if (null === $error) { |
|
532 |
+ self::$exitCode = 255; |
|
533 |
+ } |
|
534 |
+ if (!$exception instanceof \Exception) { |
|
535 |
+ $exception = new FatalThrowableError($exception); |
|
536 |
+ } |
|
537 |
+ $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR; |
|
538 |
+ $handlerException = null; |
|
539 |
+ |
|
540 |
+ if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) { |
|
541 |
+ if (false !== strpos($message = $exception->getMessage(), "class@anonymous\0")) { |
|
542 |
+ $message = (new FlattenException())->setMessage($message)->getMessage(); |
|
543 |
+ } |
|
544 |
+ if ($exception instanceof FatalErrorException) { |
|
545 |
+ if ($exception instanceof FatalThrowableError) { |
|
546 |
+ $error = [ |
|
547 |
+ 'type' => $type, |
|
548 |
+ 'message' => $message, |
|
549 |
+ 'file' => $exception->getFile(), |
|
550 |
+ 'line' => $exception->getLine(), |
|
551 |
+ ]; |
|
552 |
+ } else { |
|
553 |
+ $message = 'Fatal '.$message; |
|
554 |
+ } |
|
555 |
+ } elseif ($exception instanceof \ErrorException) { |
|
556 |
+ $message = 'Uncaught '.$message; |
|
557 |
+ } else { |
|
558 |
+ $message = 'Uncaught Exception: '.$message; |
|
559 |
+ } |
|
560 |
+ } |
|
561 |
+ if ($this->loggedErrors & $type) { |
|
562 |
+ try { |
|
563 |
+ $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]); |
|
564 |
+ } catch (\Throwable $handlerException) { |
|
565 |
+ } |
|
566 |
+ } |
|
567 |
+ if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) { |
|
568 |
+ foreach ($this->getFatalErrorHandlers() as $handler) { |
|
569 |
+ if ($e = $handler->handleError($error, $exception)) { |
|
570 |
+ $exception = $e; |
|
571 |
+ break; |
|
572 |
+ } |
|
573 |
+ } |
|
574 |
+ } |
|
575 |
+ $exceptionHandler = $this->exceptionHandler; |
|
576 |
+ $this->exceptionHandler = null; |
|
577 |
+ try { |
|
578 |
+ if (null !== $exceptionHandler) { |
|
579 |
+ return $exceptionHandler($exception); |
|
580 |
+ } |
|
581 |
+ $handlerException = $handlerException ?: $exception; |
|
582 |
+ } catch (\Throwable $handlerException) { |
|
583 |
+ } |
|
584 |
+ if ($exception === $handlerException) { |
|
585 |
+ self::$reservedMemory = null; // Disable the fatal error handler |
|
586 |
+ throw $exception; // Give back $exception to the native handler |
|
587 |
+ } |
|
588 |
+ $this->handleException($handlerException); |
|
589 |
+ } |
|
590 |
+ |
|
591 |
+ /** |
|
592 |
+ * Shutdown registered function for handling PHP fatal errors. |
|
593 |
+ * |
|
594 |
+ * @param array $error An array as returned by error_get_last() |
|
595 |
+ * |
|
596 |
+ * @internal |
|
597 |
+ */ |
|
598 |
+ public static function handleFatalError(array $error = null) |
|
599 |
+ { |
|
600 |
+ if (null === self::$reservedMemory) { |
|
601 |
+ return; |
|
602 |
+ } |
|
603 |
+ |
|
604 |
+ $handler = self::$reservedMemory = null; |
|
605 |
+ $handlers = []; |
|
606 |
+ $previousHandler = null; |
|
607 |
+ $sameHandlerLimit = 10; |
|
608 |
+ |
|
609 |
+ while (!\is_array($handler) || !$handler[0] instanceof self) { |
|
610 |
+ $handler = set_exception_handler('var_dump'); |
|
611 |
+ restore_exception_handler(); |
|
612 |
+ |
|
613 |
+ if (!$handler) { |
|
614 |
+ break; |
|
615 |
+ } |
|
616 |
+ restore_exception_handler(); |
|
617 |
+ |
|
618 |
+ if ($handler !== $previousHandler) { |
|
619 |
+ array_unshift($handlers, $handler); |
|
620 |
+ $previousHandler = $handler; |
|
621 |
+ } elseif (0 === --$sameHandlerLimit) { |
|
622 |
+ $handler = null; |
|
623 |
+ break; |
|
624 |
+ } |
|
625 |
+ } |
|
626 |
+ foreach ($handlers as $h) { |
|
627 |
+ set_exception_handler($h); |
|
628 |
+ } |
|
629 |
+ if (!$handler) { |
|
630 |
+ return; |
|
631 |
+ } |
|
632 |
+ if ($handler !== $h) { |
|
633 |
+ $handler[0]->setExceptionHandler($h); |
|
634 |
+ } |
|
635 |
+ $handler = $handler[0]; |
|
636 |
+ $handlers = []; |
|
637 |
+ |
|
638 |
+ if ($exit = null === $error) { |
|
639 |
+ $error = error_get_last(); |
|
640 |
+ } |
|
641 |
+ |
|
642 |
+ if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) { |
|
643 |
+ // Let's not throw anymore but keep logging |
|
644 |
+ $handler->throwAt(0, true); |
|
645 |
+ $trace = isset($error['backtrace']) ? $error['backtrace'] : null; |
|
646 |
+ |
|
647 |
+ if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) { |
|
648 |
+ $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace); |
|
649 |
+ } else { |
|
650 |
+ $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace); |
|
651 |
+ } |
|
652 |
+ } else { |
|
653 |
+ $exception = null; |
|
654 |
+ } |
|
655 |
+ |
|
656 |
+ try { |
|
657 |
+ if (null !== $exception) { |
|
658 |
+ self::$exitCode = 255; |
|
659 |
+ $handler->handleException($exception, $error); |
|
660 |
+ } |
|
661 |
+ } catch (FatalErrorException $e) { |
|
662 |
+ // Ignore this re-throw |
|
663 |
+ } |
|
664 |
+ |
|
665 |
+ if ($exit && self::$exitCode) { |
|
666 |
+ $exitCode = self::$exitCode; |
|
667 |
+ register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); }); |
|
668 |
+ } |
|
669 |
+ } |
|
670 |
+ |
|
671 |
+ /** |
|
672 |
+ * Gets the fatal error handlers. |
|
673 |
+ * |
|
674 |
+ * Override this method if you want to define more fatal error handlers. |
|
675 |
+ * |
|
676 |
+ * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface |
|
677 |
+ */ |
|
678 |
+ protected function getFatalErrorHandlers() |
|
679 |
+ { |
|
680 |
+ return [ |
|
681 |
+ new UndefinedFunctionFatalErrorHandler(), |
|
682 |
+ new UndefinedMethodFatalErrorHandler(), |
|
683 |
+ new ClassNotFoundFatalErrorHandler(), |
|
684 |
+ ]; |
|
685 |
+ } |
|
686 |
+ |
|
687 |
+ /** |
|
688 |
+ * Cleans the trace by removing function arguments and the frames added by the error handler and DebugClassLoader. |
|
689 |
+ */ |
|
690 |
+ private function cleanTrace($backtrace, $type, $file, $line, $throw) |
|
691 |
+ { |
|
692 |
+ $lightTrace = $backtrace; |
|
693 |
+ |
|
694 |
+ for ($i = 0; isset($backtrace[$i]); ++$i) { |
|
695 |
+ if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { |
|
696 |
+ $lightTrace = \array_slice($lightTrace, 1 + $i); |
|
697 |
+ break; |
|
698 |
+ } |
|
699 |
+ } |
|
700 |
+ if (class_exists(DebugClassLoader::class, false)) { |
|
701 |
+ for ($i = \count($lightTrace) - 2; 0 < $i; --$i) { |
|
702 |
+ if (DebugClassLoader::class === ($lightTrace[$i]['class'] ?? null)) { |
|
703 |
+ array_splice($lightTrace, --$i, 2); |
|
704 |
+ } |
|
705 |
+ } |
|
706 |
+ } |
|
707 |
+ if (!($throw || $this->scopedErrors & $type)) { |
|
708 |
+ for ($i = 0; isset($lightTrace[$i]); ++$i) { |
|
709 |
+ unset($lightTrace[$i]['args'], $lightTrace[$i]['object']); |
|
710 |
+ } |
|
711 |
+ } |
|
712 |
+ |
|
713 |
+ return $lightTrace; |
|
714 |
+ } |
|
715 |
+} |