ErrorHandler.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Debug;
  11. use Psr\Log\LoggerInterface;
  12. use Psr\Log\LogLevel;
  13. use Symfony\Component\Debug\Exception\ContextErrorException;
  14. use Symfony\Component\Debug\Exception\FatalErrorException;
  15. use Symfony\Component\Debug\Exception\FatalThrowableError;
  16. use Symfony\Component\Debug\Exception\OutOfMemoryException;
  17. use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
  18. use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
  19. use Symfony\Component\Debug\FatalErrorHandler\UndefinedFunctionFatalErrorHandler;
  20. use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
  21. /**
  22. * A generic ErrorHandler for the PHP engine.
  23. *
  24. * Provides five bit fields that control how errors are handled:
  25. * - thrownErrors: errors thrown as \ErrorException
  26. * - loggedErrors: logged errors, when not @-silenced
  27. * - scopedErrors: errors thrown or logged with their local context
  28. * - tracedErrors: errors logged with their stack trace, only once for repeated errors
  29. * - screamedErrors: never @-silenced errors
  30. *
  31. * Each error level can be logged by a dedicated PSR-3 logger object.
  32. * Screaming only applies to logging.
  33. * Throwing takes precedence over logging.
  34. * Uncaught exceptions are logged as E_ERROR.
  35. * E_DEPRECATED and E_USER_DEPRECATED levels never throw.
  36. * E_RECOVERABLE_ERROR and E_USER_ERROR levels always throw.
  37. * Non catchable errors that can be detected at shutdown time are logged when the scream bit field allows so.
  38. * As errors have a performance cost, repeated errors are all logged, so that the developer
  39. * can see them and weight them as more important to fix than others of the same level.
  40. *
  41. * @author Nicolas Grekas <p@tchwork.com>
  42. */
  43. class ErrorHandler
  44. {
  45. /**
  46. * @deprecated since version 2.6, to be removed in 3.0.
  47. */
  48. const TYPE_DEPRECATION = -100;
  49. private $levels = array(
  50. E_DEPRECATED => 'Deprecated',
  51. E_USER_DEPRECATED => 'User Deprecated',
  52. E_NOTICE => 'Notice',
  53. E_USER_NOTICE => 'User Notice',
  54. E_STRICT => 'Runtime Notice',
  55. E_WARNING => 'Warning',
  56. E_USER_WARNING => 'User Warning',
  57. E_COMPILE_WARNING => 'Compile Warning',
  58. E_CORE_WARNING => 'Core Warning',
  59. E_USER_ERROR => 'User Error',
  60. E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
  61. E_COMPILE_ERROR => 'Compile Error',
  62. E_PARSE => 'Parse Error',
  63. E_ERROR => 'Error',
  64. E_CORE_ERROR => 'Core Error',
  65. );
  66. private $loggers = array(
  67. E_DEPRECATED => array(null, LogLevel::INFO),
  68. E_USER_DEPRECATED => array(null, LogLevel::INFO),
  69. E_NOTICE => array(null, LogLevel::WARNING),
  70. E_USER_NOTICE => array(null, LogLevel::WARNING),
  71. E_STRICT => array(null, LogLevel::WARNING),
  72. E_WARNING => array(null, LogLevel::WARNING),
  73. E_USER_WARNING => array(null, LogLevel::WARNING),
  74. E_COMPILE_WARNING => array(null, LogLevel::WARNING),
  75. E_CORE_WARNING => array(null, LogLevel::WARNING),
  76. E_USER_ERROR => array(null, LogLevel::CRITICAL),
  77. E_RECOVERABLE_ERROR => array(null, LogLevel::CRITICAL),
  78. E_COMPILE_ERROR => array(null, LogLevel::CRITICAL),
  79. E_PARSE => array(null, LogLevel::CRITICAL),
  80. E_ERROR => array(null, LogLevel::CRITICAL),
  81. E_CORE_ERROR => array(null, LogLevel::CRITICAL),
  82. );
  83. private $thrownErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
  84. private $scopedErrors = 0x1FFF; // E_ALL - E_DEPRECATED - E_USER_DEPRECATED
  85. private $tracedErrors = 0x77FB; // E_ALL - E_STRICT - E_PARSE
  86. private $screamedErrors = 0x55; // E_ERROR + E_CORE_ERROR + E_COMPILE_ERROR + E_PARSE
  87. private $loggedErrors = 0;
  88. private $loggedTraces = array();
  89. private $isRecursive = 0;
  90. private $isRoot = false;
  91. private $exceptionHandler;
  92. private $bootstrappingLogger;
  93. private static $reservedMemory;
  94. private static $stackedErrors = array();
  95. private static $stackedErrorLevels = array();
  96. private static $toStringException = null;
  97. private static $exitCode = 0;
  98. /**
  99. * Same init value as thrownErrors.
  100. *
  101. * @deprecated since version 2.6, to be removed in 3.0.
  102. */
  103. private $displayErrors = 0x1FFF;
  104. /**
  105. * Registers the error handler.
  106. *
  107. * @param self|int|null $handler The handler to register, or @deprecated (since version 2.6, to be removed in 3.0) bit field of thrown levels
  108. * @param bool $replace Whether to replace or not any existing handler
  109. *
  110. * @return self The registered error handler
  111. */
  112. public static function register($handler = null, $replace = true)
  113. {
  114. if (null === self::$reservedMemory) {
  115. self::$reservedMemory = str_repeat('x', 10240);
  116. register_shutdown_function(__CLASS__.'::handleFatalError');
  117. }
  118. $levels = -1;
  119. if ($handlerIsNew = !$handler instanceof self) {
  120. // @deprecated polymorphism, to be removed in 3.0
  121. if (null !== $handler) {
  122. $levels = $replace ? $handler : 0;
  123. $replace = true;
  124. }
  125. $handler = new static();
  126. }
  127. if (null === $prev = set_error_handler(array($handler, 'handleError'))) {
  128. restore_error_handler();
  129. // Specifying the error types earlier would expose us to https://bugs.php.net/63206
  130. set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors);
  131. $handler->isRoot = true;
  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(array($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(array($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. $handler->throwAt($levels & $handler->thrownErrors, true);
  157. return $handler;
  158. }
  159. public function __construct(BufferingLogger $bootstrappingLogger = null)
  160. {
  161. if ($bootstrappingLogger) {
  162. $this->bootstrappingLogger = $bootstrappingLogger;
  163. $this->setDefaultLogger($bootstrappingLogger);
  164. }
  165. }
  166. /**
  167. * Sets a logger to non assigned errors levels.
  168. *
  169. * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels
  170. * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
  171. * @param bool $replace Whether to replace or not any existing logger
  172. */
  173. public function setDefaultLogger(LoggerInterface $logger, $levels = null, $replace = false)
  174. {
  175. $loggers = array();
  176. if (\is_array($levels)) {
  177. foreach ($levels as $type => $logLevel) {
  178. if (empty($this->loggers[$type][0]) || $replace || $this->loggers[$type][0] === $this->bootstrappingLogger) {
  179. $loggers[$type] = array($logger, $logLevel);
  180. }
  181. }
  182. } else {
  183. if (null === $levels) {
  184. $levels = E_ALL | E_STRICT;
  185. }
  186. foreach ($this->loggers as $type => $log) {
  187. if (($type & $levels) && (empty($log[0]) || $replace || $log[0] === $this->bootstrappingLogger)) {
  188. $log[0] = $logger;
  189. $loggers[$type] = $log;
  190. }
  191. }
  192. }
  193. $this->setLoggers($loggers);
  194. }
  195. /**
  196. * Sets a logger for each error level.
  197. *
  198. * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
  199. *
  200. * @return array The previous map
  201. *
  202. * @throws \InvalidArgumentException
  203. */
  204. public function setLoggers(array $loggers)
  205. {
  206. $prevLogged = $this->loggedErrors;
  207. $prev = $this->loggers;
  208. $flush = array();
  209. foreach ($loggers as $type => $log) {
  210. if (!isset($prev[$type])) {
  211. throw new \InvalidArgumentException('Unknown error type: '.$type);
  212. }
  213. if (!\is_array($log)) {
  214. $log = array($log);
  215. } elseif (!array_key_exists(0, $log)) {
  216. throw new \InvalidArgumentException('No logger provided');
  217. }
  218. if (null === $log[0]) {
  219. $this->loggedErrors &= ~$type;
  220. } elseif ($log[0] instanceof LoggerInterface) {
  221. $this->loggedErrors |= $type;
  222. } else {
  223. throw new \InvalidArgumentException('Invalid logger provided');
  224. }
  225. $this->loggers[$type] = $log + $prev[$type];
  226. if ($this->bootstrappingLogger && $prev[$type][0] === $this->bootstrappingLogger) {
  227. $flush[$type] = $type;
  228. }
  229. }
  230. $this->reRegister($prevLogged | $this->thrownErrors);
  231. if ($flush) {
  232. foreach ($this->bootstrappingLogger->cleanLogs() as $log) {
  233. $type = $log[2]['type'];
  234. if (!isset($flush[$type])) {
  235. $this->bootstrappingLogger->log($log[0], $log[1], $log[2]);
  236. } elseif ($this->loggers[$type][0]) {
  237. $this->loggers[$type][0]->log($this->loggers[$type][1], $log[1], $log[2]);
  238. }
  239. }
  240. }
  241. return $prev;
  242. }
  243. /**
  244. * Sets a user exception handler.
  245. *
  246. * @param callable $handler A handler that will be called on Exception
  247. *
  248. * @return callable|null The previous exception handler
  249. *
  250. * @throws \InvalidArgumentException
  251. */
  252. public function setExceptionHandler($handler)
  253. {
  254. if (null !== $handler && !\is_callable($handler)) {
  255. throw new \LogicException('The exception handler must be a valid PHP callable.');
  256. }
  257. $prev = $this->exceptionHandler;
  258. $this->exceptionHandler = $handler;
  259. return $prev;
  260. }
  261. /**
  262. * Sets the PHP error levels that throw an exception when a PHP error occurs.
  263. *
  264. * @param int $levels A bit field of E_* constants for thrown errors
  265. * @param bool $replace Replace or amend the previous value
  266. *
  267. * @return int The previous value
  268. */
  269. public function throwAt($levels, $replace = false)
  270. {
  271. $prev = $this->thrownErrors;
  272. $this->thrownErrors = ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
  273. if (!$replace) {
  274. $this->thrownErrors |= $prev;
  275. }
  276. $this->reRegister($prev | $this->loggedErrors);
  277. // $this->displayErrors is @deprecated since version 2.6
  278. $this->displayErrors = $this->thrownErrors;
  279. return $prev;
  280. }
  281. /**
  282. * Sets the PHP error levels for which local variables are preserved.
  283. *
  284. * @param int $levels A bit field of E_* constants for scoped errors
  285. * @param bool $replace Replace or amend the previous value
  286. *
  287. * @return int The previous value
  288. */
  289. public function scopeAt($levels, $replace = false)
  290. {
  291. $prev = $this->scopedErrors;
  292. $this->scopedErrors = (int) $levels;
  293. if (!$replace) {
  294. $this->scopedErrors |= $prev;
  295. }
  296. return $prev;
  297. }
  298. /**
  299. * Sets the PHP error levels for which the stack trace is preserved.
  300. *
  301. * @param int $levels A bit field of E_* constants for traced errors
  302. * @param bool $replace Replace or amend the previous value
  303. *
  304. * @return int The previous value
  305. */
  306. public function traceAt($levels, $replace = false)
  307. {
  308. $prev = $this->tracedErrors;
  309. $this->tracedErrors = (int) $levels;
  310. if (!$replace) {
  311. $this->tracedErrors |= $prev;
  312. }
  313. return $prev;
  314. }
  315. /**
  316. * Sets the error levels where the @-operator is ignored.
  317. *
  318. * @param int $levels A bit field of E_* constants for screamed errors
  319. * @param bool $replace Replace or amend the previous value
  320. *
  321. * @return int The previous value
  322. */
  323. public function screamAt($levels, $replace = false)
  324. {
  325. $prev = $this->screamedErrors;
  326. $this->screamedErrors = (int) $levels;
  327. if (!$replace) {
  328. $this->screamedErrors |= $prev;
  329. }
  330. return $prev;
  331. }
  332. /**
  333. * Re-registers as a PHP error handler if levels changed.
  334. */
  335. private function reRegister($prev)
  336. {
  337. if ($prev !== $this->thrownErrors | $this->loggedErrors) {
  338. $handler = set_error_handler('var_dump');
  339. $handler = \is_array($handler) ? $handler[0] : null;
  340. restore_error_handler();
  341. if ($handler === $this) {
  342. restore_error_handler();
  343. if ($this->isRoot) {
  344. set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
  345. } else {
  346. set_error_handler(array($this, 'handleError'));
  347. }
  348. }
  349. }
  350. }
  351. /**
  352. * Handles errors by filtering then logging them according to the configured bit fields.
  353. *
  354. * @param int $type One of the E_* constants
  355. * @param string $message
  356. * @param string $file
  357. * @param int $line
  358. *
  359. * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself
  360. *
  361. * @throws \ErrorException When $this->thrownErrors requests so
  362. *
  363. * @internal
  364. */
  365. public function handleError($type, $message, $file, $line)
  366. {
  367. $level = error_reporting();
  368. $silenced = 0 === ($level & $type);
  369. $level |= E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
  370. $log = $this->loggedErrors & $type;
  371. $throw = $this->thrownErrors & $type & $level;
  372. $type &= $level | $this->screamedErrors;
  373. if (!$type || (!$log && !$throw)) {
  374. return !$silenced && $type && $log;
  375. }
  376. $scope = $this->scopedErrors & $type;
  377. if (4 < $numArgs = \func_num_args()) {
  378. $context = $scope ? (func_get_arg(4) ?: array()) : array();
  379. $backtrace = 5 < $numArgs ? func_get_arg(5) : null; // defined on HHVM
  380. } else {
  381. $context = array();
  382. $backtrace = null;
  383. }
  384. if (isset($context['GLOBALS']) && $scope) {
  385. $e = $context; // Whatever the signature of the method,
  386. unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
  387. $context = $e;
  388. }
  389. if (null !== $backtrace && $type & E_ERROR) {
  390. // E_ERROR fatal errors are triggered on HHVM when
  391. // hhvm.error_handling.call_user_handler_on_fatals=1
  392. // which is the way to get their backtrace.
  393. $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));
  394. return true;
  395. }
  396. if ($throw) {
  397. if (null !== self::$toStringException) {
  398. $throw = self::$toStringException;
  399. self::$toStringException = null;
  400. } elseif ($scope && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
  401. // Checking for class existence is a work around for https://bugs.php.net/42098
  402. $throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
  403. } else {
  404. $throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
  405. }
  406. if (\PHP_VERSION_ID <= 50407 && (\PHP_VERSION_ID >= 50400 || \PHP_VERSION_ID <= 50317)) {
  407. // Exceptions thrown from error handlers are sometimes not caught by the exception
  408. // handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
  409. // We temporarily re-enable display_errors to prevent any blank page related to this bug.
  410. $throw->errorHandlerCanary = new ErrorHandlerCanary();
  411. }
  412. if (E_USER_ERROR & $type) {
  413. $backtrace = $backtrace ?: $throw->getTrace();
  414. for ($i = 1; isset($backtrace[$i]); ++$i) {
  415. if (isset($backtrace[$i]['function'], $backtrace[$i]['type'], $backtrace[$i - 1]['function'])
  416. && '__toString' === $backtrace[$i]['function']
  417. && '->' === $backtrace[$i]['type']
  418. && !isset($backtrace[$i - 1]['class'])
  419. && ('trigger_error' === $backtrace[$i - 1]['function'] || 'user_error' === $backtrace[$i - 1]['function'])
  420. ) {
  421. // Here, we know trigger_error() has been called from __toString().
  422. // HHVM is fine with throwing from __toString() but PHP triggers a fatal error instead.
  423. // A small convention allows working around the limitation:
  424. // given a caught $e exception in __toString(), quitting the method with
  425. // `return trigger_error($e, E_USER_ERROR);` allows this error handler
  426. // to make $e get through the __toString() barrier.
  427. foreach ($context as $e) {
  428. if (($e instanceof \Exception || $e instanceof \Throwable) && $e->__toString() === $message) {
  429. if (1 === $i) {
  430. // On HHVM
  431. $throw = $e;
  432. break;
  433. }
  434. self::$toStringException = $e;
  435. return true;
  436. }
  437. }
  438. if (1 < $i) {
  439. // On PHP (not on HHVM), display the original error message instead of the default one.
  440. $this->handleException($throw);
  441. // Stop the process by giving back the error to the native handler.
  442. return false;
  443. }
  444. }
  445. }
  446. }
  447. throw $throw;
  448. }
  449. // For duplicated errors, log the trace only once
  450. $e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
  451. $trace = true;
  452. if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
  453. $trace = false;
  454. } else {
  455. $this->loggedTraces[$e] = 1;
  456. }
  457. $e = compact('type', 'file', 'line', 'level');
  458. if ($type & $level) {
  459. if ($scope) {
  460. $e['scope_vars'] = $context;
  461. if ($trace) {
  462. $e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
  463. }
  464. } elseif ($trace) {
  465. if (null === $backtrace) {
  466. $e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  467. } else {
  468. foreach ($backtrace as &$frame) {
  469. unset($frame['args'], $frame);
  470. }
  471. $e['stack'] = $backtrace;
  472. }
  473. }
  474. }
  475. if ($this->isRecursive) {
  476. $log = 0;
  477. } elseif (self::$stackedErrorLevels) {
  478. self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
  479. } else {
  480. try {
  481. $this->isRecursive = true;
  482. $this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
  483. $this->isRecursive = false;
  484. } catch (\Exception $e) {
  485. $this->isRecursive = false;
  486. throw $e;
  487. } catch (\Throwable $e) {
  488. $this->isRecursive = false;
  489. throw $e;
  490. }
  491. }
  492. return !$silenced && $type && $log;
  493. }
  494. /**
  495. * Handles an exception by logging then forwarding it to another handler.
  496. *
  497. * @param \Exception|\Throwable $exception An exception to handle
  498. * @param array $error An array as returned by error_get_last()
  499. *
  500. * @internal
  501. */
  502. public function handleException($exception, array $error = null)
  503. {
  504. if (null === $error) {
  505. self::$exitCode = 255;
  506. }
  507. if (!$exception instanceof \Exception) {
  508. $exception = new FatalThrowableError($exception);
  509. }
  510. $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
  511. $handlerException = null;
  512. if (($this->loggedErrors & $type) || $exception instanceof FatalThrowableError) {
  513. $e = array(
  514. 'type' => $type,
  515. 'file' => $exception->getFile(),
  516. 'line' => $exception->getLine(),
  517. 'level' => error_reporting(),
  518. 'stack' => $exception->getTrace(),
  519. );
  520. if ($exception instanceof FatalErrorException) {
  521. if ($exception instanceof FatalThrowableError) {
  522. $error = array(
  523. 'type' => $type,
  524. 'message' => $message = $exception->getMessage(),
  525. 'file' => $e['file'],
  526. 'line' => $e['line'],
  527. );
  528. } else {
  529. $message = 'Fatal '.$exception->getMessage();
  530. }
  531. } elseif ($exception instanceof \ErrorException) {
  532. $message = 'Uncaught '.$exception->getMessage();
  533. if ($exception instanceof ContextErrorException) {
  534. $e['context'] = $exception->getContext();
  535. }
  536. } else {
  537. $message = 'Uncaught Exception: '.$exception->getMessage();
  538. }
  539. }
  540. if ($this->loggedErrors & $type) {
  541. try {
  542. $this->loggers[$type][0]->log($this->loggers[$type][1], $message, $e);
  543. } catch (\Exception $handlerException) {
  544. } catch (\Throwable $handlerException) {
  545. }
  546. }
  547. if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
  548. foreach ($this->getFatalErrorHandlers() as $handler) {
  549. if ($e = $handler->handleError($error, $exception)) {
  550. $exception = $e;
  551. break;
  552. }
  553. }
  554. }
  555. $exceptionHandler = $this->exceptionHandler;
  556. $this->exceptionHandler = null;
  557. try {
  558. if (null !== $exceptionHandler) {
  559. return \call_user_func($exceptionHandler, $exception);
  560. }
  561. $handlerException = $handlerException ?: $exception;
  562. } catch (\Exception $handlerException) {
  563. } catch (\Throwable $handlerException) {
  564. }
  565. if ($exception === $handlerException) {
  566. self::$reservedMemory = null; // Disable the fatal error handler
  567. throw $exception; // Give back $exception to the native handler
  568. }
  569. $this->handleException($handlerException);
  570. }
  571. /**
  572. * Shutdown registered function for handling PHP fatal errors.
  573. *
  574. * @param array $error An array as returned by error_get_last()
  575. *
  576. * @internal
  577. */
  578. public static function handleFatalError(array $error = null)
  579. {
  580. if (null === self::$reservedMemory) {
  581. return;
  582. }
  583. $handler = self::$reservedMemory = null;
  584. $handlers = array();
  585. $previousHandler = null;
  586. $sameHandlerLimit = 10;
  587. while (!\is_array($handler) || !$handler[0] instanceof self) {
  588. $handler = set_exception_handler('var_dump');
  589. restore_exception_handler();
  590. if (!$handler) {
  591. break;
  592. }
  593. restore_exception_handler();
  594. if ($handler !== $previousHandler) {
  595. array_unshift($handlers, $handler);
  596. $previousHandler = $handler;
  597. } elseif (0 === --$sameHandlerLimit) {
  598. $handler = null;
  599. break;
  600. }
  601. }
  602. foreach ($handlers as $h) {
  603. set_exception_handler($h);
  604. }
  605. if (!$handler) {
  606. return;
  607. }
  608. if ($handler !== $h) {
  609. $handler[0]->setExceptionHandler($h);
  610. }
  611. $handler = $handler[0];
  612. $handlers = array();
  613. if ($exit = null === $error) {
  614. $error = error_get_last();
  615. }
  616. try {
  617. while (self::$stackedErrorLevels) {
  618. static::unstackErrors();
  619. }
  620. } catch (\Exception $exception) {
  621. // Handled below
  622. } catch (\Throwable $exception) {
  623. // Handled below
  624. }
  625. if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
  626. // Let's not throw anymore but keep logging
  627. $handler->throwAt(0, true);
  628. $trace = isset($error['backtrace']) ? $error['backtrace'] : null;
  629. if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
  630. $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
  631. } else {
  632. $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
  633. }
  634. }
  635. try {
  636. if (isset($exception)) {
  637. self::$exitCode = 255;
  638. $handler->handleException($exception, $error);
  639. }
  640. } catch (FatalErrorException $e) {
  641. // Ignore this re-throw
  642. }
  643. if ($exit && self::$exitCode) {
  644. $exitCode = self::$exitCode;
  645. register_shutdown_function('register_shutdown_function', function () use ($exitCode) { exit($exitCode); });
  646. }
  647. }
  648. /**
  649. * Configures the error handler for delayed handling.
  650. * Ensures also that non-catchable fatal errors are never silenced.
  651. *
  652. * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
  653. * PHP has a compile stage where it behaves unusually. To workaround it,
  654. * we plug an error handler that only stacks errors for later.
  655. *
  656. * The most important feature of this is to prevent
  657. * autoloading until unstackErrors() is called.
  658. */
  659. public static function stackErrors()
  660. {
  661. self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
  662. }
  663. /**
  664. * Unstacks stacked errors and forwards to the logger.
  665. */
  666. public static function unstackErrors()
  667. {
  668. $level = array_pop(self::$stackedErrorLevels);
  669. if (null !== $level) {
  670. $e = error_reporting($level);
  671. if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
  672. // If the user changed the error level, do not overwrite it
  673. error_reporting($e);
  674. }
  675. }
  676. if (empty(self::$stackedErrorLevels)) {
  677. $errors = self::$stackedErrors;
  678. self::$stackedErrors = array();
  679. foreach ($errors as $e) {
  680. $e[0]->log($e[1], $e[2], $e[3]);
  681. }
  682. }
  683. }
  684. /**
  685. * Gets the fatal error handlers.
  686. *
  687. * Override this method if you want to define more fatal error handlers.
  688. *
  689. * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
  690. */
  691. protected function getFatalErrorHandlers()
  692. {
  693. return array(
  694. new UndefinedFunctionFatalErrorHandler(),
  695. new UndefinedMethodFatalErrorHandler(),
  696. new ClassNotFoundFatalErrorHandler(),
  697. );
  698. }
  699. /**
  700. * Sets the level at which the conversion to Exception is done.
  701. *
  702. * @param int|null $level The level (null to use the error_reporting() value and 0 to disable)
  703. *
  704. * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
  705. */
  706. public function setLevel($level)
  707. {
  708. @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);
  709. $level = null === $level ? error_reporting() : $level;
  710. $this->throwAt($level, true);
  711. }
  712. /**
  713. * Sets the display_errors flag value.
  714. *
  715. * @param int $displayErrors The display_errors flag value
  716. *
  717. * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
  718. */
  719. public function setDisplayErrors($displayErrors)
  720. {
  721. @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);
  722. if ($displayErrors) {
  723. $this->throwAt($this->displayErrors, true);
  724. } else {
  725. $displayErrors = $this->displayErrors;
  726. $this->throwAt(0, true);
  727. $this->displayErrors = $displayErrors;
  728. }
  729. }
  730. /**
  731. * Sets a logger for the given channel.
  732. *
  733. * @param LoggerInterface $logger A logger interface
  734. * @param string $channel The channel associated with the logger (deprecation, emergency or scream)
  735. *
  736. * @deprecated since version 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead.
  737. */
  738. public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
  739. {
  740. @trigger_error('The '.__METHOD__.' static method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the setLoggers() or setDefaultLogger() methods instead.', E_USER_DEPRECATED);
  741. $handler = set_error_handler('var_dump');
  742. $handler = \is_array($handler) ? $handler[0] : null;
  743. restore_error_handler();
  744. if (!$handler instanceof self) {
  745. return;
  746. }
  747. if ('deprecation' === $channel) {
  748. $handler->setDefaultLogger($logger, E_DEPRECATED | E_USER_DEPRECATED, true);
  749. $handler->screamAt(E_DEPRECATED | E_USER_DEPRECATED);
  750. } elseif ('scream' === $channel) {
  751. $handler->setDefaultLogger($logger, E_ALL | E_STRICT, false);
  752. $handler->screamAt(E_ALL | E_STRICT);
  753. } elseif ('emergency' === $channel) {
  754. $handler->setDefaultLogger($logger, E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR, true);
  755. $handler->screamAt(E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
  756. }
  757. }
  758. /**
  759. * @deprecated since version 2.6, to be removed in 3.0. Use handleError() instead.
  760. */
  761. public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
  762. {
  763. $this->handleError(E_USER_DEPRECATED, 'The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleError() method instead.', __FILE__, __LINE__, array());
  764. return $this->handleError($level, $message, $file, $line, (array) $context);
  765. }
  766. /**
  767. * Handles PHP fatal errors.
  768. *
  769. * @deprecated since version 2.6, to be removed in 3.0. Use handleFatalError() instead.
  770. */
  771. public function handleFatal()
  772. {
  773. @trigger_error('The '.__METHOD__.' method is deprecated since Symfony 2.6 and will be removed in 3.0. Use the handleFatalError() method instead.', E_USER_DEPRECATED);
  774. static::handleFatalError();
  775. }
  776. }
  777. /**
  778. * Private class used to work around https://bugs.php.net/54275.
  779. *
  780. * @author Nicolas Grekas <p@tchwork.com>
  781. *
  782. * @internal
  783. */
  784. class ErrorHandlerCanary
  785. {
  786. private static $displayErrors = null;
  787. public function __construct()
  788. {
  789. if (null === self::$displayErrors) {
  790. self::$displayErrors = ini_set('display_errors', 1);
  791. }
  792. }
  793. public function __destruct()
  794. {
  795. if (null !== self::$displayErrors) {
  796. ini_set('display_errors', self::$displayErrors);
  797. self::$displayErrors = null;
  798. }
  799. }
  800. }