You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

263 lines
8.6KB

  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\base;
  8. use Yii;
  9. use yii\helpers\VarDumper;
  10. use yii\web\HttpException;
  11. /**
  12. * ErrorHandler handles uncaught PHP errors and exceptions.
  13. *
  14. * ErrorHandler is configured as an application component in [[\yii\base\Application]] by default.
  15. * You can access that instance via `Yii::$app->errorHandler`.
  16. *
  17. * @author Qiang Xue <qiang.xue@gmail.com>
  18. * @author Alexander Makarov <sam@rmcreative.ru>
  19. * @author Carsten Brandt <mail@cebe.cc>
  20. * @since 2.0
  21. */
  22. abstract class ErrorHandler extends Component
  23. {
  24. /**
  25. * @var boolean whether to discard any existing page output before error display. Defaults to true.
  26. */
  27. public $discardExistingOutput = true;
  28. /**
  29. * @var integer the size of the reserved memory. A portion of memory is pre-allocated so that
  30. * when an out-of-memory issue occurs, the error handler is able to handle the error with
  31. * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
  32. * Defaults to 256KB.
  33. */
  34. public $memoryReserveSize = 262144;
  35. /**
  36. * @var \Exception the exception that is being handled currently.
  37. */
  38. public $exception;
  39. /**
  40. * @var string Used to reserve memory for fatal error handler.
  41. */
  42. private $_memoryReserve;
  43. /**
  44. * Register this error handler
  45. */
  46. public function register()
  47. {
  48. ini_set('display_errors', false);
  49. set_exception_handler([$this, 'handleException']);
  50. set_error_handler([$this, 'handleError']);
  51. if ($this->memoryReserveSize > 0) {
  52. $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
  53. }
  54. register_shutdown_function([$this, 'handleFatalError']);
  55. }
  56. /**
  57. * Unregisters this error handler by restoring the PHP error and exception handlers.
  58. */
  59. public function unregister()
  60. {
  61. restore_error_handler();
  62. restore_exception_handler();
  63. }
  64. /**
  65. * Handles uncaught PHP exceptions.
  66. *
  67. * This method is implemented as a PHP exception handler.
  68. *
  69. * @param \Exception $exception the exception that is not caught
  70. */
  71. public function handleException($exception)
  72. {
  73. if ($exception instanceof ExitException) {
  74. return;
  75. }
  76. $this->exception = $exception;
  77. // disable error capturing to avoid recursive errors while handling exceptions
  78. $this->unregister();
  79. try {
  80. $this->logException($exception);
  81. if ($this->discardExistingOutput) {
  82. $this->clearOutput();
  83. }
  84. $this->renderException($exception);
  85. if (!YII_ENV_TEST) {
  86. exit(1);
  87. }
  88. } catch (\Exception $e) {
  89. // an other exception could be thrown while displaying the exception
  90. $msg = (string) $e;
  91. $msg .= "\nPrevious exception:\n";
  92. $msg .= (string) $exception;
  93. if (YII_DEBUG) {
  94. if (PHP_SAPI === 'cli') {
  95. echo $msg . "\n";
  96. } else {
  97. echo '<pre>' . htmlspecialchars($msg, ENT_QUOTES, Yii::$app->charset) . '</pre>';
  98. }
  99. }
  100. $msg .= "\n\$_SERVER = " . VarDumper::export($_SERVER);
  101. error_log($msg);
  102. exit(1);
  103. }
  104. $this->exception = null;
  105. }
  106. /**
  107. * Handles PHP execution errors such as warnings and notices.
  108. *
  109. * This method is used as a PHP error handler. It will simply raise an [[ErrorException]].
  110. *
  111. * @param integer $code the level of the error raised.
  112. * @param string $message the error message.
  113. * @param string $file the filename that the error was raised in.
  114. * @param integer $line the line number the error was raised at.
  115. * @return boolean whether the normal error handler continues.
  116. *
  117. * @throws ErrorException
  118. */
  119. public function handleError($code, $message, $file, $line)
  120. {
  121. if (error_reporting() & $code) {
  122. // load ErrorException manually here because autoloading them will not work
  123. // when error occurs while autoloading a class
  124. if (!class_exists('yii\\base\\ErrorException', false)) {
  125. require_once(__DIR__ . '/ErrorException.php');
  126. }
  127. $exception = new ErrorException($message, $code, $code, $file, $line);
  128. // in case error appeared in __toString method we can't throw any exception
  129. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  130. array_shift($trace);
  131. foreach ($trace as $frame) {
  132. if ($frame['function'] == '__toString') {
  133. $this->handleException($exception);
  134. exit(1);
  135. }
  136. }
  137. throw $exception;
  138. }
  139. return false;
  140. }
  141. /**
  142. * Handles fatal PHP errors
  143. */
  144. public function handleFatalError()
  145. {
  146. unset($this->_memoryReserve);
  147. // load ErrorException manually here because autoloading them will not work
  148. // when error occurs while autoloading a class
  149. if (!class_exists('yii\\base\\ErrorException', false)) {
  150. require_once(__DIR__ . '/ErrorException.php');
  151. }
  152. $error = error_get_last();
  153. if (ErrorException::isFatalError($error)) {
  154. $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
  155. $this->exception = $exception;
  156. $this->logException($exception);
  157. if ($this->discardExistingOutput) {
  158. $this->clearOutput();
  159. }
  160. $this->renderException($exception);
  161. // need to explicitly flush logs because exit() next will terminate the app immediately
  162. Yii::getLogger()->flush(true);
  163. exit(1);
  164. }
  165. }
  166. /**
  167. * Renders the exception.
  168. * @param \Exception $exception the exception to be rendered.
  169. */
  170. abstract protected function renderException($exception);
  171. /**
  172. * Logs the given exception
  173. * @param \Exception $exception the exception to be logged
  174. * @since 2.0.3 this method is now public.
  175. */
  176. public function logException($exception)
  177. {
  178. $category = get_class($exception);
  179. if ($exception instanceof HttpException) {
  180. $category = 'yii\\web\\HttpException:' . $exception->statusCode;
  181. } elseif ($exception instanceof \ErrorException) {
  182. $category .= ':' . $exception->getSeverity();
  183. }
  184. Yii::error((string) $exception, $category);
  185. }
  186. /**
  187. * Removes all output echoed before calling this method.
  188. */
  189. public function clearOutput()
  190. {
  191. // the following manual level counting is to deal with zlib.output_compression set to On
  192. for ($level = ob_get_level(); $level > 0; --$level) {
  193. if (!@ob_end_clean()) {
  194. ob_clean();
  195. }
  196. }
  197. }
  198. /**
  199. * Converts an exception into a PHP error.
  200. *
  201. * This method can be used to convert exceptions inside of methods like `__toString()`
  202. * to PHP errors because exceptions cannot be thrown inside of them.
  203. * @param \Exception $exception the exception to convert to a PHP error.
  204. */
  205. public static function convertExceptionToError($exception)
  206. {
  207. trigger_error(static::convertExceptionToString($exception), E_USER_ERROR);
  208. }
  209. /**
  210. * Converts an exception into a simple string.
  211. * @param \Exception $exception the exception being converted
  212. * @return string the string representation of the exception.
  213. */
  214. public static function convertExceptionToString($exception)
  215. {
  216. if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
  217. $message = "{$exception->getName()}: {$exception->getMessage()}";
  218. } elseif (YII_DEBUG) {
  219. if ($exception instanceof Exception) {
  220. $message = "Exception ({$exception->getName()})";
  221. } elseif ($exception instanceof ErrorException) {
  222. $message = "{$exception->getName()}";
  223. } else {
  224. $message = 'Exception';
  225. }
  226. $message .= " '" . get_class($exception) . "' with message '{$exception->getMessage()}' \n\nin "
  227. . $exception->getFile() . ':' . $exception->getLine() . "\n\n"
  228. . "Stack trace:\n" . $exception->getTraceAsString();
  229. } else {
  230. $message = 'Error: ' . $exception->getMessage();
  231. }
  232. return $message;
  233. }
  234. }