Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

ErrorHandler.php

Zuletzt modifiziert: 02.04.2025, 15:02 - Dateigröße: 29.34 KiB


001  <?php
002   
003  /*
004   * This file is part of the Symfony package.
005   *
006   * (c) Fabien Potencier <fabien@symfony.com>
007   *
008   * For the full copyright and license information, please view the LICENSE
009   * file that was distributed with this source code.
010   */
011   
012  namespace Symfony\Component\Debug;
013   
014  use Psr\Log\LoggerInterface;
015  use Psr\Log\LogLevel;
016  use Symfony\Component\Debug\Exception\ContextErrorException;
017  use Symfony\Component\Debug\Exception\FatalErrorException;
018  use Symfony\Component\Debug\Exception\FatalThrowableError;
019  use Symfony\Component\Debug\Exception\OutOfMemoryException;
020  use Symfony\Component\Debug\Exception\SilencedErrorContext;
021  use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
022  use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
023  use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
024  use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
025   
026  /**
027   * A generic ErrorHandler for the PHP engine.
028   *
029   * Provides five bit fields that control how errors are handled:
030   * - thrownErrors: errors thrown as \ErrorException
031   * - loggedErrors: logged errors, when not @-silenced
032   * - scopedErrors: errors thrown or logged with their local context
033   * - tracedErrors: errors logged with their stack trace
034   * - screamedErrors: never @-silenced errors
035   *
036   * Each error level can be logged by a dedicated PSR-3 logger object.
037   * Screaming only applies to logging.
038   * Throwing takes precedence over logging.
039   * Uncaught exceptions are logged as E_ERROR.
040   * E_DEPRECATED and E_USER_DEPRECATED levels never throw.
041   * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
042   * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
043   * As errors have a performance cost, repeated errors are all logged, so that the developer
044   * can see them and weight them as more important to fix than others of the same level.
045   *
046   * @author Nicolas Grekas <p@tchwork.com>
047   * @author Grégoire Pineau <lyrixx@lyrixx.info>
048   */
049  class ErrorHandler
050  {
051      private $levels = [
052          \E_DEPRECATED => 'Deprecated',
053          \E_USER_DEPRECATED => 'User Deprecated',
054          \E_NOTICE => 'Notice',
055          \E_USER_NOTICE => 'User Notice',
056          \E_STRICT => 'Runtime Notice',
057          \E_WARNING => 'Warning',
058          \E_USER_WARNING => 'User Warning',
059          \E_COMPILE_WARNING => 'Compile Warning',
060          \E_CORE_WARNING => 'Core Warning',
061          \E_USER_ERROR => 'User Error',
062          \E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
063          \E_COMPILE_ERROR => 'Compile Error',
064          \E_PARSE => 'Parse Error',
065          \E_ERROR => 'Error',
066          \E_CORE_ERROR => 'Core Error',
067      ];
068   
069      private $loggers = [
070          \E_DEPRECATED => [null, LogLevel::INFO],
071          \E_USER_DEPRECATED => [null, LogLevel::INFO],
072          \E_NOTICE => [null, LogLevel::WARNING],
073          \E_USER_NOTICE => [null, LogLevel::WARNING],
074          \E_STRICT => [null, LogLevel::WARNING],
075          \E_WARNING => [null, LogLevel::WARNING],
076          \E_USER_WARNING => [null, LogLevel::WARNING],
077          \E_COMPILE_WARNING => [null, LogLevel::WARNING],
078          \E_CORE_WARNING => [null, LogLevel::WARNING],
079          \E_USER_ERROR => [null, LogLevel::CRITICAL],
080          \E_RECOVERABLE_ERROR => [null, LogLevel::CRITICAL],
081          \E_COMPILE_ERROR => [null, LogLevel::CRITICAL],
082          \E_PARSE => [null, LogLevel::CRITICAL],
083          \E_ERROR => [null, LogLevel::CRITICAL],
084          \E_CORE_ERROR => [null, LogLevel::CRITICAL],
085      ];
086   
087      private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
088      private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
089      private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
090      private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
091      private $loggedErrors = 0;
092      private $traceReflector;
093   
094      private $isRecursive = 0;
095      private $isRoot = false;
096      private $exceptionHandler;
097      private $bootstrappingLogger;
098   
099      private static $reservedMemory;
100      private static $stackedErrors = [];
101      private static $stackedErrorLevels = [];
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          if (\PHP_VERSION_ID >= 70300 && \E_WARNING === $type && '"' === $message[0] && false !== strpos($message, '" targeting switch is equivalent to "break')) {
386              $type = \E_DEPRECATED;
387          }
388   
389          // Level is the current error reporting level to manage silent error.
390          $level = error_reporting();
391          $silenced = 0 === ($level & $type);
392          // Strong errors are not authorized to be silenced.
393          $level |= \E_RECOVERABLE_ERROR | \E_USER_ERROR | \E_DEPRECATED | \E_USER_DEPRECATED;
394          $log = $this->loggedErrors & $type;
395          $throw = $this->thrownErrors & $type & $level;
396          $type &= $level | $this->screamedErrors;
397   
398          if (!$type || (!$log && !$throw)) {
399              return !$silenced && $type && $log;
400          }
401          $scope = $this->scopedErrors & $type;
402   
403          if (4 < $numArgs = \func_num_args()) {
404              $context = func_get_arg(4) ?: [];
405              $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM
406          } else {
407              $context = [];
408              $backtrace = null;
409          }
410   
411          if (isset($context['GLOBALS']) && $scope) {
412              $e = $context;                  // Whatever the signature of the method,
413              unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
414              $context = $e;
415          }
416   
417          if (null !== $backtrace && $type & \E_ERROR) {
418              // E_ERROR fatal errors are triggered on HHVM when
419              // hhvm.error_handling.call_user_handler_on_fatals=1
420              // which is the way to get their backtrace.
421              $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));
422   
423              return true;
424          }
425   
426          $logMessage = $this->levels[$type].': '.$message;
427   
428          if (null !== self::$toStringException) {
429              $errorAsException = self::$toStringException;
430              self::$toStringException = null;
431          } elseif (!$throw && !($type & $level)) {
432              if (!isset(self::$silencedErrorCache[$id = $file.':'.$line])) {
433                  $lightTrace = $this->tracedErrors & $type ? $this->cleanTrace(debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 3), $type, $file, $line, false) : [];
434                  $errorAsException = new SilencedErrorContext($type, $file, $line, $lightTrace);
435              } elseif (isset(self::$silencedErrorCache[$id][$message])) {
436                  $lightTrace = null;
437                  $errorAsException = self::$silencedErrorCache[$id][$message];
438                  ++$errorAsException->count;
439              } else {
440                  $lightTrace = [];
441                  $errorAsException = null;
442              }
443   
444              if (100 < ++self::$silencedErrorCount) {
445                  self::$silencedErrorCache = $lightTrace = [];
446                  self::$silencedErrorCount = 1;
447              }
448              if ($errorAsException) {
449                  self::$silencedErrorCache[$id][$message] = $errorAsException;
450              }
451              if (null === $lightTrace) {
452                  return true;
453              }
454          } else {
455              if ($scope) {
456                  $errorAsException = new ContextErrorException($logMessage, 0, $type, $file, $line, $context);
457              } else {
458                  $errorAsException = new \ErrorException($logMessage, 0, $type, $file, $line);
459              }
460   
461              // Clean the trace by removing function arguments and the first frames added by the error handler itself.
462              if ($throw || $this->tracedErrors & $type) {
463                  $backtrace = $backtrace ?: $errorAsException->getTrace();
464                  $lightTrace = $this->cleanTrace($backtrace, $type, $file, $line, $throw);
465                  $this->traceReflector->setValue($errorAsException, $lightTrace);
466              } else {
467                  $this->traceReflector->setValue($errorAsException, []);
468              }
469          }
470   
471          if ($throw) {
472              if (\PHP_VERSION_ID < 70400 && \E_USER_ERROR & $type) {
473                  for ($i = 1; isset($backtrace[$i]); ++$i) {
474                      if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
475                          && '__toString' === $backtrace[$i]['function']
476                          && '->' === $backtrace[$i]['type']
477                          && !isset($backtrace[$i - 1]['class'])
478                          && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
479                      ) {
480                          // Here, we know trigger_error() has been called from __toString().
481                          // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
482                          // A small convention allows working around the limitation:
483                          // given a caught $e exception in __toString(), quitting the method with
484                          // `return trigger_error($e, E_USER_ERROR);` allows this error handler
485                          // to make $e get through the __toString() barrier.
486   
487                          foreach ($context as $e) {
488                              if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
489                                  if (1 === $i) {
490                                      // On HHVM
491                                      $errorAsException = $e;
492                                      break;
493                                  }
494                                  self::$toStringException = $e;
495   
496                                  return true;
497                              }
498                          }
499   
500                          if (1 < $i) {
501                              // On PHP (not on HHVM), display the original error message instead of the default one.
502                              $this->handleException($errorAsException);
503   
504                              // Stop the process by giving back the error to the native handler.
505                              return false;
506                          }
507                      }
508                  }
509              }
510   
511              throw $errorAsException;
512          }
513   
514          if ($this->isRecursive) {
515              $log = 0;
516          } elseif (self::$stackedErrorLevels) {
517              self::$stackedErrors[] = [
518                  $this->loggers[$type][0],
519                  ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG,
520                  $logMessage,
521                  $errorAsException ? ['exception' => $errorAsException] : [],
522              ];
523          } else {
524              if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404) && !\defined('HHVM_VERSION')) {
525                  $currentErrorHandler = set_error_handler('var_dump');
526                  restore_error_handler();
527              }
528   
529              try {
530                  $this->isRecursive = true;
531                  $level = ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG;
532                  $this->loggers[$type][0]->log($level, $logMessage, $errorAsException ? ['exception' => $errorAsException] : []);
533              } finally {
534                  $this->isRecursive = false;
535   
536                  if (\PHP_VERSION_ID < (\PHP_VERSION_ID < 70400 ? 70316 : 70404) && !\defined('HHVM_VERSION')) {
537                      set_error_handler($currentErrorHandler);
538                  }
539              }
540          }
541   
542          return !$silenced && $type && $log;
543      }
544   
545      /**
546       * Handles an exception by logging then forwarding it to another handler.
547       *
548       * @param \Exception|\Throwable $exception An exception to handle
549       * @param array                 $error     An array as returned by error_get_last()
550       *
551       * @internal
552       */
553      public function handleException($exception, array $error = null)
554      {
555          if (null === $error) {
556              self::$exitCode = 255;
557          }
558          if (!$exception instanceof \Exception) {
559              $exception = new FatalThrowableError($exception);
560          }
561          $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : \E_ERROR;
562          $handlerException = null;
563   
564          if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
565              if ($exception instanceof FatalErrorException) {
566                  if ($exception instanceof FatalThrowableError) {
567                      $error = [
568                          'type' => $type,
569                          'message' => $message = $exception->getMessage(),
570                          'file' => $exception->getFile(),
571                          'line' => $exception->getLine(),
572                      ];
573                  } else {
574                      $message = 'Fatal '.$exception->getMessage();
575                  }
576              } elseif ($exception instanceof \ErrorException) {
577                  $message = 'Uncaught '.$exception->getMessage();
578              } else {
579                  $message = 'Uncaught Exception: '.$exception->getMessage();
580              }
581          }
582          if ($this->loggedErrors & $type) {
583              try {
584                  $this->loggers[$type][0]->log($this->loggers[$type][1], $message, ['exception' => $exception]);
585              } catch (\Exception $handlerException) {
586              } catch (\Throwable $handlerException) {
587              }
588          }
589          if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
590              foreach ($this->getFatalErrorHandlers() as $handler) {
591                  if ($e = $handler->handleError($error, $exception)) {
592                      $exception = $e;
593                      break;
594                  }
595              }
596          }
597          $exceptionHandler = $this->exceptionHandler;
598          $this->exceptionHandler = null;
599          try {
600              if (null !== $exceptionHandler) {
601                  $exceptionHandler($exception);
602   
603                  return;
604              }
605              $handlerException = $handlerException ?: $exception;
606          } catch (\Exception $handlerException) {
607          } catch (\Throwable $handlerException) {
608          }
609          if ($exception === $handlerException) {
610              self::$reservedMemory = null; // Disable the fatal error handler
611              throw $exception; // Give back $exception to the native handler
612          }
613          $this->handleException($handlerException);
614      }
615   
616      /**
617       * Shutdown registered function for handling PHP fatal errors.
618       *
619       * @param array $error An array as returned by error_get_last()
620       *
621       * @internal
622       */
623      public static function handleFatalError(array $error = null)
624      {
625          if (null === self::$reservedMemory) {
626              return;
627          }
628   
629          $handler = self::$reservedMemory = null;
630          $handlers = [];
631          $previousHandler = null;
632          $sameHandlerLimit = 10;
633   
634          while (!\is_array($handler) || !$handler[0] instanceof self) {
635              $handler = set_exception_handler('var_dump');
636              restore_exception_handler();
637   
638              if (!$handler) {
639                  break;
640              }
641              restore_exception_handler();
642   
643              if ($handler !== $previousHandler) {
644                  array_unshift($handlers, $handler);
645                  $previousHandler = $handler;
646              } elseif (0 === --$sameHandlerLimit) {
647                  $handler = null;
648                  break;
649              }
650          }
651          foreach ($handlers as $h) {
652              set_exception_handler($h);
653          }
654          if (!$handler) {
655              return;
656          }
657          if ($handler !== $h) {
658              $handler[0]->setExceptionHandler($h);
659          }
660          $handler = $handler[0];
661          $handlers = [];
662   
663          if ($exit = null === $error) {
664              $error = error_get_last();
665          }
666   
667          try {
668              while (self::$stackedErrorLevels) {
669                  static::unstackErrors();
670              }
671          } catch (\Exception $exception) {
672              // Handled below
673          } catch (\Throwable $exception) {
674              // Handled below
675          }
676   
677          if ($error && $error['type'] &= \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR) {
678              // Let's not throw anymore but keep logging
679              $handler->throwAt(0, true);
680              $trace = isset($error['backtrace']) ? $error['backtrace'] : null;
681   
682              if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
683                  $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
684              } else {
685                  $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
686              }
687          }
688   
689          try {
690              if (isset($exception)) {
691                  self::$exitCode = 255;
692                  $handler->handleException($exception, $error);
693              }
694          } catch (FatalErrorException $e) {
695              // Ignore this re-throw
696          }
697   
698          if ($exit && self::$exitCode) {
699              $exitCode = self::$exitCode;
700              register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
701          }
702      }
703   
704      /**
705       * Configures the error handler for delayed handling.
706       * Ensures also that non-catchable fatal errors are never silenced.
707       *
708       * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
709       * PHP has a compile stage where it behaves unusually. To workaround it,
710       * we plug an error handler that only stacks errors for later.
711       *
712       * The most important feature of this is to prevent
713       * autoloading until unstackErrors() is called.
714       *
715       * @deprecated since version 3.4, to be removed in 4.0.
716       */
717      public static function stackErrors()
718      {
719          @trigger_error('Support for stacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED);
720   
721          self::$stackedErrorLevels[] = error_reporting(error_reporting() | \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR);
722      }
723   
724      /**
725       * Unstacks stacked errors and forwards to the logger.
726       *
727       * @deprecated since version 3.4, to be removed in 4.0.
728       */
729      public static function unstackErrors()
730      {
731          @trigger_error('Support for unstacking errors is deprecated since Symfony 3.4 and will be removed in 4.0.', \E_USER_DEPRECATED);
732   
733          $level = array_pop(self::$stackedErrorLevels);
734   
735          if (null !== $level) {
736              $errorReportingLevel = error_reporting($level);
737              if ($errorReportingLevel !== ($level | \E_PARSE | \E_ERROR | \E_CORE_ERROR | \E_COMPILE_ERROR)) {
738                  // If the user changed the error level, do not overwrite it
739                  error_reporting($errorReportingLevel);
740              }
741          }
742   
743          if (empty(self::$stackedErrorLevels)) {
744              $errors = self::$stackedErrors;
745              self::$stackedErrors = [];
746   
747              foreach ($errors as $error) {
748                  $error[0]->log($error[1], $error[2], $error[3]);
749              }
750          }
751      }
752   
753      /**
754       * Gets the fatal error handlers.
755       *
756       * Override this method if you want to define more fatal error handlers.
757       *
758       * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
759       */
760      protected function getFatalErrorHandlers()
761      {
762          return [
763              new UndefinedFunctionFatalErrorHandler(),
764              new UndefinedMethodFatalErrorHandler(),
765              new ClassNotFoundFatalErrorHandler(),
766          ];
767      }
768   
769      private function cleanTrace($backtrace, $type, $file, $line, $throw)
770      {
771          $lightTrace = $backtrace;
772   
773          for ($i = 0; isset($backtrace[$i]); ++$i) {
774              if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) {
775                  $lightTrace = \array_slice($lightTrace, 1 + $i);
776                  break;
777              }
778          }
779          if (!($throw || $this->scopedErrors & $type)) {
780              for ($i = 0; isset($lightTrace[$i]); ++$i) {
781                  unset($lightTrace[$i]['args'], $lightTrace[$i]['object']);
782              }
783          }
784   
785          return $lightTrace;
786      }
787  }
788