ErrorHandler.php 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741
  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\LogLevel;
  12. use Psr\Log\LoggerInterface;
  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\UndefinedFunctionFatalErrorHandler;
  18. use Symfony\Component\Debug\FatalErrorHandler\UndefinedMethodFatalErrorHandler;
  19. use Symfony\Component\Debug\FatalErrorHandler\ClassNotFoundFatalErrorHandler;
  20. use Symfony\Component\Debug\FatalErrorHandler\FatalErrorHandlerInterface;
  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 $exceptionHandler;
  91. private static $reservedMemory;
  92. private static $stackedErrors = array();
  93. private static $stackedErrorLevels = array();
  94. /**
  95. * Same init value as thrownErrors.
  96. *
  97. * @deprecated since version 2.6, to be removed in 3.0.
  98. */
  99. private $displayErrors = 0x1FFF;
  100. /**
  101. * Registers the error handler.
  102. *
  103. * @param self|null|int $handler The handler to register, or @deprecated (since version 2.6, to be removed in 3.0) bit field of thrown levels
  104. * @param bool $replace Whether to replace or not any existing handler
  105. *
  106. * @return self The registered error handler
  107. */
  108. public static function register($handler = null, $replace = true)
  109. {
  110. if (null === self::$reservedMemory) {
  111. self::$reservedMemory = str_repeat('x', 10240);
  112. register_shutdown_function(__CLASS__.'::handleFatalError');
  113. }
  114. $levels = -1;
  115. if ($handlerIsNew = !$handler instanceof self) {
  116. // @deprecated polymorphism, to be removed in 3.0
  117. if (null !== $handler) {
  118. $levels = $replace ? $handler : 0;
  119. $replace = true;
  120. }
  121. $handler = new static();
  122. }
  123. $prev = set_error_handler(array($handler, 'handleError'), $handler->thrownErrors | $handler->loggedErrors);
  124. if ($handlerIsNew && is_array($prev) && $prev[0] instanceof self) {
  125. $handler = $prev[0];
  126. $replace = false;
  127. }
  128. if ($replace || !$prev) {
  129. $handler->setExceptionHandler(set_exception_handler(array($handler, 'handleException')));
  130. } else {
  131. restore_error_handler();
  132. }
  133. $handler->throwAt($levels & $handler->thrownErrors, true);
  134. return $handler;
  135. }
  136. /**
  137. * Sets a logger to non assigned errors levels.
  138. *
  139. * @param LoggerInterface $logger A PSR-3 logger to put as default for the given levels
  140. * @param array|int $levels An array map of E_* to LogLevel::* or an integer bit field of E_* constants
  141. * @param bool $replace Whether to replace or not any existing logger
  142. */
  143. public function setDefaultLogger(LoggerInterface $logger, $levels = null, $replace = false)
  144. {
  145. $loggers = array();
  146. if (is_array($levels)) {
  147. foreach ($levels as $type => $logLevel) {
  148. if (empty($this->loggers[$type][0]) || $replace) {
  149. $loggers[$type] = array($logger, $logLevel);
  150. }
  151. }
  152. } else {
  153. if (null === $levels) {
  154. $levels = E_ALL | E_STRICT;
  155. }
  156. foreach ($this->loggers as $type => $log) {
  157. if (($type & $levels) && (empty($log[0]) || $replace)) {
  158. $log[0] = $logger;
  159. $loggers[$type] = $log;
  160. }
  161. }
  162. }
  163. $this->setLoggers($loggers);
  164. }
  165. /**
  166. * Sets a logger for each error level.
  167. *
  168. * @param array $loggers Error levels to [LoggerInterface|null, LogLevel::*] map
  169. *
  170. * @return array The previous map
  171. *
  172. * @throws \InvalidArgumentException
  173. */
  174. public function setLoggers(array $loggers)
  175. {
  176. $prevLogged = $this->loggedErrors;
  177. $prev = $this->loggers;
  178. foreach ($loggers as $type => $log) {
  179. if (!isset($prev[$type])) {
  180. throw new \InvalidArgumentException('Unknown error type: '.$type);
  181. }
  182. if (!is_array($log)) {
  183. $log = array($log);
  184. } elseif (!array_key_exists(0, $log)) {
  185. throw new \InvalidArgumentException('No logger provided');
  186. }
  187. if (null === $log[0]) {
  188. $this->loggedErrors &= ~$type;
  189. } elseif ($log[0] instanceof LoggerInterface) {
  190. $this->loggedErrors |= $type;
  191. } else {
  192. throw new \InvalidArgumentException('Invalid logger provided');
  193. }
  194. $this->loggers[$type] = $log + $prev[$type];
  195. }
  196. $this->reRegister($prevLogged | $this->thrownErrors);
  197. return $prev;
  198. }
  199. /**
  200. * Sets a user exception handler.
  201. *
  202. * @param callable $handler A handler that will be called on Exception
  203. *
  204. * @return callable|null The previous exception handler
  205. *
  206. * @throws \InvalidArgumentException
  207. */
  208. public function setExceptionHandler($handler)
  209. {
  210. if (null !== $handler && !is_callable($handler)) {
  211. throw new \LogicException('The exception handler must be a valid PHP callable.');
  212. }
  213. $prev = $this->exceptionHandler;
  214. $this->exceptionHandler = $handler;
  215. return $prev;
  216. }
  217. /**
  218. * Sets the PHP error levels that throw an exception when a PHP error occurs.
  219. *
  220. * @param int $levels A bit field of E_* constants for thrown errors
  221. * @param bool $replace Replace or amend the previous value
  222. *
  223. * @return int The previous value
  224. */
  225. public function throwAt($levels, $replace = false)
  226. {
  227. $prev = $this->thrownErrors;
  228. $this->thrownErrors = (E_ALL | E_STRICT) & ($levels | E_RECOVERABLE_ERROR | E_USER_ERROR) & ~E_USER_DEPRECATED & ~E_DEPRECATED;
  229. if (!$replace) {
  230. $this->thrownErrors |= $prev;
  231. }
  232. $this->reRegister($prev | $this->loggedErrors);
  233. // $this->displayErrors is @deprecated since version 2.6
  234. $this->displayErrors = $this->thrownErrors;
  235. return $prev;
  236. }
  237. /**
  238. * Sets the PHP error levels for which local variables are preserved.
  239. *
  240. * @param int $levels A bit field of E_* constants for scoped errors
  241. * @param bool $replace Replace or amend the previous value
  242. *
  243. * @return int The previous value
  244. */
  245. public function scopeAt($levels, $replace = false)
  246. {
  247. $prev = $this->scopedErrors;
  248. $this->scopedErrors = (int) $levels;
  249. if (!$replace) {
  250. $this->scopedErrors |= $prev;
  251. }
  252. return $prev;
  253. }
  254. /**
  255. * Sets the PHP error levels for which the stack trace is preserved.
  256. *
  257. * @param int $levels A bit field of E_* constants for traced errors
  258. * @param bool $replace Replace or amend the previous value
  259. *
  260. * @return int The previous value
  261. */
  262. public function traceAt($levels, $replace = false)
  263. {
  264. $prev = $this->tracedErrors;
  265. $this->tracedErrors = (int) $levels;
  266. if (!$replace) {
  267. $this->tracedErrors |= $prev;
  268. }
  269. return $prev;
  270. }
  271. /**
  272. * Sets the error levels where the @-operator is ignored.
  273. *
  274. * @param int $levels A bit field of E_* constants for screamed errors
  275. * @param bool $replace Replace or amend the previous value
  276. *
  277. * @return int The previous value
  278. */
  279. public function screamAt($levels, $replace = false)
  280. {
  281. $prev = $this->screamedErrors;
  282. $this->screamedErrors = (int) $levels;
  283. if (!$replace) {
  284. $this->screamedErrors |= $prev;
  285. }
  286. return $prev;
  287. }
  288. /**
  289. * Re-registers as a PHP error handler if levels changed.
  290. */
  291. private function reRegister($prev)
  292. {
  293. if ($prev !== $this->thrownErrors | $this->loggedErrors) {
  294. $handler = set_error_handler('var_dump', 0);
  295. $handler = is_array($handler) ? $handler[0] : null;
  296. restore_error_handler();
  297. if ($handler === $this) {
  298. restore_error_handler();
  299. set_error_handler(array($this, 'handleError'), $this->thrownErrors | $this->loggedErrors);
  300. }
  301. }
  302. }
  303. /**
  304. * Handles errors by filtering then logging them according to the configured bit fields.
  305. *
  306. * @param int $type One of the E_* constants
  307. * @param string $file
  308. * @param int $line
  309. * @param array $context
  310. *
  311. * @return bool Returns false when no handling happens so that the PHP engine can handle the error itself.
  312. *
  313. * @throws \ErrorException When $this->thrownErrors requests so
  314. *
  315. * @internal
  316. */
  317. public function handleError($type, $message, $file, $line, array $context, array $backtrace = null)
  318. {
  319. $level = error_reporting() | E_RECOVERABLE_ERROR | E_USER_ERROR | E_DEPRECATED | E_USER_DEPRECATED;
  320. $log = $this->loggedErrors & $type;
  321. $throw = $this->thrownErrors & $type & $level;
  322. $type &= $level | $this->screamedErrors;
  323. if (!$type || (!$log && !$throw)) {
  324. return $type && $log;
  325. }
  326. if (PHP_VERSION_ID < 50400 && isset($context['GLOBALS']) && ($this->scopedErrors & $type)) {
  327. $e = $context; // Whatever the signature of the method,
  328. unset($e['GLOBALS'], $context); // $context is always a reference in 5.3
  329. $context = $e;
  330. }
  331. if (null !== $backtrace && $type & E_ERROR) {
  332. // E_ERROR fatal errors are triggered on HHVM when
  333. // hhvm.error_handling.call_user_handler_on_fatals=1
  334. // which is the way to get their backtrace.
  335. $this->handleFatalError(compact('type', 'message', 'file', 'line', 'backtrace'));
  336. return true;
  337. }
  338. if ($throw) {
  339. if (($this->scopedErrors & $type) && class_exists('Symfony\Component\Debug\Exception\ContextErrorException')) {
  340. // Checking for class existence is a work around for https://bugs.php.net/42098
  341. $throw = new ContextErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line, $context);
  342. } else {
  343. $throw = new \ErrorException($this->levels[$type].': '.$message, 0, $type, $file, $line);
  344. }
  345. if (PHP_VERSION_ID <= 50407 && (PHP_VERSION_ID >= 50400 || PHP_VERSION_ID <= 50317)) {
  346. // Exceptions thrown from error handlers are sometimes not caught by the exception
  347. // handler and shutdown handlers are bypassed before 5.4.8/5.3.18.
  348. // We temporarily re-enable display_errors to prevent any blank page related to this bug.
  349. $throw->errorHandlerCanary = new ErrorHandlerCanary();
  350. }
  351. throw $throw;
  352. }
  353. // For duplicated errors, log the trace only once
  354. $e = md5("{$type}/{$line}/{$file}\x00{$message}", true);
  355. $trace = true;
  356. if (!($this->tracedErrors & $type) || isset($this->loggedTraces[$e])) {
  357. $trace = false;
  358. } else {
  359. $this->loggedTraces[$e] = 1;
  360. }
  361. $e = compact('type', 'file', 'line', 'level');
  362. if ($type & $level) {
  363. if ($this->scopedErrors & $type) {
  364. $e['scope_vars'] = $context;
  365. if ($trace) {
  366. $e['stack'] = $backtrace ?: debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT);
  367. }
  368. } elseif ($trace) {
  369. if (null === $backtrace) {
  370. $e['stack'] = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  371. } else {
  372. foreach ($backtrace as &$frame) {
  373. unset($frame['args'], $frame);
  374. }
  375. $e['stack'] = $backtrace;
  376. }
  377. }
  378. }
  379. if ($this->isRecursive) {
  380. $log = 0;
  381. } elseif (self::$stackedErrorLevels) {
  382. self::$stackedErrors[] = array($this->loggers[$type][0], ($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
  383. } else {
  384. try {
  385. $this->isRecursive = true;
  386. $this->loggers[$type][0]->log(($type & $level) ? $this->loggers[$type][1] : LogLevel::DEBUG, $message, $e);
  387. $this->isRecursive = false;
  388. } catch (\Exception $e) {
  389. $this->isRecursive = false;
  390. throw $e;
  391. }
  392. }
  393. return $type && $log;
  394. }
  395. /**
  396. * Handles an exception by logging then forwarding it to another handler.
  397. *
  398. * @param \Exception|\Throwable $exception An exception to handle
  399. * @param array $error An array as returned by error_get_last()
  400. *
  401. * @internal
  402. */
  403. public function handleException($exception, array $error = null)
  404. {
  405. if (!$exception instanceof \Exception) {
  406. $exception = new FatalThrowableError($exception);
  407. }
  408. $type = $exception instanceof FatalErrorException ? $exception->getSeverity() : E_ERROR;
  409. if ($this->loggedErrors & $type) {
  410. $e = array(
  411. 'type' => $type,
  412. 'file' => $exception->getFile(),
  413. 'line' => $exception->getLine(),
  414. 'level' => error_reporting(),
  415. 'stack' => $exception->getTrace(),
  416. );
  417. if ($exception instanceof FatalErrorException) {
  418. if ($exception instanceof FatalThrowableError) {
  419. $error = array(
  420. 'type' => $type,
  421. 'message' => $message = $exception->getMessage(),
  422. 'file' => $e['file'],
  423. 'line' => $e['line'],
  424. );
  425. } else {
  426. $message = 'Fatal '.$exception->getMessage();
  427. }
  428. } elseif ($exception instanceof \ErrorException) {
  429. $message = 'Uncaught '.$exception->getMessage();
  430. if ($exception instanceof ContextErrorException) {
  431. $e['context'] = $exception->getContext();
  432. }
  433. } else {
  434. $message = 'Uncaught Exception: '.$exception->getMessage();
  435. }
  436. if ($this->loggedErrors & $e['type']) {
  437. $this->loggers[$e['type']][0]->log($this->loggers[$e['type']][1], $message, $e);
  438. }
  439. }
  440. if ($exception instanceof FatalErrorException && !$exception instanceof OutOfMemoryException && $error) {
  441. foreach ($this->getFatalErrorHandlers() as $handler) {
  442. if ($e = $handler->handleError($error, $exception)) {
  443. $exception = $e;
  444. break;
  445. }
  446. }
  447. }
  448. if (empty($this->exceptionHandler)) {
  449. throw $exception; // Give back $exception to the native handler
  450. }
  451. try {
  452. call_user_func($this->exceptionHandler, $exception);
  453. } catch (\Exception $handlerException) {
  454. } catch (\Throwable $handlerException) {
  455. }
  456. if (isset($handlerException)) {
  457. $this->exceptionHandler = null;
  458. $this->handleException($handlerException);
  459. }
  460. }
  461. /**
  462. * Shutdown registered function for handling PHP fatal errors.
  463. *
  464. * @param array $error An array as returned by error_get_last()
  465. *
  466. * @internal
  467. */
  468. public static function handleFatalError(array $error = null)
  469. {
  470. if (null === self::$reservedMemory) {
  471. return;
  472. }
  473. self::$reservedMemory = null;
  474. $handler = set_error_handler('var_dump', 0);
  475. $handler = is_array($handler) ? $handler[0] : null;
  476. restore_error_handler();
  477. if (!$handler instanceof self) {
  478. return;
  479. }
  480. if (null === $error) {
  481. $error = error_get_last();
  482. }
  483. try {
  484. while (self::$stackedErrorLevels) {
  485. static::unstackErrors();
  486. }
  487. } catch (\Exception $exception) {
  488. // Handled below
  489. }
  490. if ($error && $error['type'] &= E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR) {
  491. // Let's not throw anymore but keep logging
  492. $handler->throwAt(0, true);
  493. $trace = isset($error['backtrace']) ? $error['backtrace'] : null;
  494. if (0 === strpos($error['message'], 'Allowed memory') || 0 === strpos($error['message'], 'Out of memory')) {
  495. $exception = new OutOfMemoryException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, false, $trace);
  496. } else {
  497. $exception = new FatalErrorException($handler->levels[$error['type']].': '.$error['message'], 0, $error['type'], $error['file'], $error['line'], 2, true, $trace);
  498. }
  499. } elseif (!isset($exception)) {
  500. return;
  501. }
  502. try {
  503. $handler->handleException($exception, $error);
  504. } catch (FatalErrorException $e) {
  505. // Ignore this re-throw
  506. }
  507. }
  508. /**
  509. * Configures the error handler for delayed handling.
  510. * Ensures also that non-catchable fatal errors are never silenced.
  511. *
  512. * As shown by http://bugs.php.net/42098 and http://bugs.php.net/60724
  513. * PHP has a compile stage where it behaves unusually. To workaround it,
  514. * we plug an error handler that only stacks errors for later.
  515. *
  516. * The most important feature of this is to prevent
  517. * autoloading until unstackErrors() is called.
  518. */
  519. public static function stackErrors()
  520. {
  521. self::$stackedErrorLevels[] = error_reporting(error_reporting() | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
  522. }
  523. /**
  524. * Unstacks stacked errors and forwards to the logger.
  525. */
  526. public static function unstackErrors()
  527. {
  528. $level = array_pop(self::$stackedErrorLevels);
  529. if (null !== $level) {
  530. $e = error_reporting($level);
  531. if ($e !== ($level | E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR)) {
  532. // If the user changed the error level, do not overwrite it
  533. error_reporting($e);
  534. }
  535. }
  536. if (empty(self::$stackedErrorLevels)) {
  537. $errors = self::$stackedErrors;
  538. self::$stackedErrors = array();
  539. foreach ($errors as $e) {
  540. $e[0]->log($e[1], $e[2], $e[3]);
  541. }
  542. }
  543. }
  544. /**
  545. * Gets the fatal error handlers.
  546. *
  547. * Override this method if you want to define more fatal error handlers.
  548. *
  549. * @return FatalErrorHandlerInterface[] An array of FatalErrorHandlerInterface
  550. */
  551. protected function getFatalErrorHandlers()
  552. {
  553. return array(
  554. new UndefinedFunctionFatalErrorHandler(),
  555. new UndefinedMethodFatalErrorHandler(),
  556. new ClassNotFoundFatalErrorHandler(),
  557. );
  558. }
  559. /**
  560. * Sets the level at which the conversion to Exception is done.
  561. *
  562. * @param int|null $level The level (null to use the error_reporting() value and 0 to disable)
  563. *
  564. * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
  565. */
  566. public function setLevel($level)
  567. {
  568. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);
  569. $level = null === $level ? error_reporting() : $level;
  570. $this->throwAt($level, true);
  571. }
  572. /**
  573. * Sets the display_errors flag value.
  574. *
  575. * @param int $displayErrors The display_errors flag value
  576. *
  577. * @deprecated since version 2.6, to be removed in 3.0. Use throwAt() instead.
  578. */
  579. public function setDisplayErrors($displayErrors)
  580. {
  581. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the throwAt() method instead.', E_USER_DEPRECATED);
  582. if ($displayErrors) {
  583. $this->throwAt($this->displayErrors, true);
  584. } else {
  585. $displayErrors = $this->displayErrors;
  586. $this->throwAt(0, true);
  587. $this->displayErrors = $displayErrors;
  588. }
  589. }
  590. /**
  591. * Sets a logger for the given channel.
  592. *
  593. * @param LoggerInterface $logger A logger interface
  594. * @param string $channel The channel associated with the logger (deprecation, emergency or scream)
  595. *
  596. * @deprecated since version 2.6, to be removed in 3.0. Use setLoggers() or setDefaultLogger() instead.
  597. */
  598. public static function setLogger(LoggerInterface $logger, $channel = 'deprecation')
  599. {
  600. @trigger_error('The '.__METHOD__.' static method is deprecated since version 2.6 and will be removed in 3.0. Use the setLoggers() or setDefaultLogger() methods instead.', E_USER_DEPRECATED);
  601. $handler = set_error_handler('var_dump', 0);
  602. $handler = is_array($handler) ? $handler[0] : null;
  603. restore_error_handler();
  604. if (!$handler instanceof self) {
  605. return;
  606. }
  607. if ('deprecation' === $channel) {
  608. $handler->setDefaultLogger($logger, E_DEPRECATED | E_USER_DEPRECATED, true);
  609. $handler->screamAt(E_DEPRECATED | E_USER_DEPRECATED);
  610. } elseif ('scream' === $channel) {
  611. $handler->setDefaultLogger($logger, E_ALL | E_STRICT, false);
  612. $handler->screamAt(E_ALL | E_STRICT);
  613. } elseif ('emergency' === $channel) {
  614. $handler->setDefaultLogger($logger, E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR, true);
  615. $handler->screamAt(E_PARSE | E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR);
  616. }
  617. }
  618. /**
  619. * @deprecated since version 2.6, to be removed in 3.0. Use handleError() instead.
  620. */
  621. public function handle($level, $message, $file = 'unknown', $line = 0, $context = array())
  622. {
  623. $this->handleError(E_USER_DEPRECATED, 'The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the handleError() method instead.', __FILE__, __LINE__, array());
  624. return $this->handleError($level, $message, $file, $line, (array) $context);
  625. }
  626. /**
  627. * Handles PHP fatal errors.
  628. *
  629. * @deprecated since version 2.6, to be removed in 3.0. Use handleFatalError() instead.
  630. */
  631. public function handleFatal()
  632. {
  633. @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6 and will be removed in 3.0. Use the handleFatalError() method instead.', E_USER_DEPRECATED);
  634. static::handleFatalError();
  635. }
  636. }
  637. /**
  638. * Private class used to work around https://bugs.php.net/54275.
  639. *
  640. * @author Nicolas Grekas <p@tchwork.com>
  641. *
  642. * @internal
  643. */
  644. class ErrorHandlerCanary
  645. {
  646. private static $displayErrors = null;
  647. public function __construct()
  648. {
  649. if (null === self::$displayErrors) {
  650. self::$displayErrors = ini_set('display_errors', 1);
  651. }
  652. }
  653. public function __destruct()
  654. {
  655. if (null !== self::$displayErrors) {
  656. ini_set('display_errors', self::$displayErrors);
  657. self::$displayErrors = null;
  658. }
  659. }
  660. }