error.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. <?php
  2. /**
  3. * Part of the Fuel framework.
  4. *
  5. * @package Fuel
  6. * @version 1.5
  7. * @author Fuel Development Team
  8. * @license MIT License
  9. * @copyright 2010 - 2013 Fuel Development Team
  10. * @link http://fuelphp.com
  11. */
  12. namespace Fuel\Core;
  13. /**
  14. * Exception class for standard PHP errors, this will make them catchable
  15. */
  16. class PhpErrorException extends \ErrorException
  17. {
  18. public static $count = 0;
  19. public function handle()
  20. {
  21. // handle the error based on the config and the environment we're in
  22. if (static::$count <= Config::get('errors.throttle', 10))
  23. {
  24. logger(\Fuel::L_ERROR, $this->code.' - '.$this->message.' in '.$this->file.' on line '.$this->line);
  25. if (\Fuel::$env != \Fuel::PRODUCTION and ($this->code & error_reporting()) == $this->code)
  26. {
  27. static::$count++;
  28. \Error::show_php_error(new \ErrorException($this->message, $this->code, 0, $this->file, $this->line));
  29. }
  30. }
  31. elseif (\Fuel::$env != \Fuel::PRODUCTION
  32. and static::$count == (\Config::get('errors.throttle', 10) + 1)
  33. and ($this->severity & error_reporting()) == $this->severity)
  34. {
  35. static::$count++;
  36. \Error::notice('Error throttling threshold was reached, no more full error reports are shown.', true);
  37. }
  38. }
  39. }
  40. /**
  41. *
  42. */
  43. class Error
  44. {
  45. public static $levels = array(
  46. 0 => 'Error',
  47. E_ERROR => 'Error',
  48. E_WARNING => 'Warning',
  49. E_PARSE => 'Parsing Error',
  50. E_NOTICE => 'Notice',
  51. E_CORE_ERROR => 'Core Error',
  52. E_CORE_WARNING => 'Core Warning',
  53. E_COMPILE_ERROR => 'Compile Error',
  54. E_COMPILE_WARNING => 'Compile Warning',
  55. E_USER_ERROR => 'User Error',
  56. E_USER_WARNING => 'User Warning',
  57. E_USER_NOTICE => 'User Notice',
  58. E_STRICT => 'Runtime Notice'
  59. );
  60. public static $fatal_levels = array(E_PARSE, E_ERROR, E_USER_ERROR, E_COMPILE_ERROR);
  61. public static $non_fatal_cache = array();
  62. /**
  63. * Native PHP shutdown handler
  64. *
  65. * @return string
  66. */
  67. public static function shutdown_handler()
  68. {
  69. $last_error = error_get_last();
  70. // Only show valid fatal errors
  71. if ($last_error AND in_array($last_error['type'], static::$fatal_levels))
  72. {
  73. $severity = static::$levels[$last_error['type']];
  74. logger(\Fuel::L_ERROR, $severity.' - '.$last_error['message'].' in '.$last_error['file'].' on line '.$last_error['line']);
  75. $error = new \ErrorException($last_error['message'], $last_error['type'], 0, $last_error['file'], $last_error['line']);
  76. if (\Fuel::$env != \Fuel::PRODUCTION)
  77. {
  78. static::show_php_error($error);
  79. }
  80. else
  81. {
  82. static::show_production_error($error);
  83. }
  84. exit(1);
  85. }
  86. }
  87. /**
  88. * PHP Exception handler
  89. *
  90. * @param Exception $e the exception
  91. * @return bool
  92. */
  93. public static function exception_handler(\Exception $e)
  94. {
  95. if (method_exists($e, 'handle'))
  96. {
  97. return $e->handle();
  98. }
  99. $severity = ( ! isset(static::$levels[$e->getCode()])) ? $e->getCode() : static::$levels[$e->getCode()];
  100. logger(\Fuel::L_ERROR, $severity.' - '.$e->getMessage().' in '.$e->getFile().' on line '.$e->getLine());
  101. if (\Fuel::$env != \Fuel::PRODUCTION)
  102. {
  103. static::show_php_error($e);
  104. }
  105. else
  106. {
  107. static::show_production_error($e);
  108. }
  109. }
  110. /**
  111. * PHP Error handler
  112. *
  113. * @param int $severity the severity code
  114. * @param string $message the error message
  115. * @param string $filepath the path to the file throwing the error
  116. * @param int $line the line number of the error
  117. * @return bool whether to continue with execution
  118. */
  119. public static function error_handler($severity, $message, $filepath, $line)
  120. {
  121. // don't do anything if error reporting is disabled
  122. if (error_reporting() !== 0)
  123. {
  124. $fatal = (bool)( ! in_array($severity, \Config::get('errors.continue_on', array())));
  125. if ($fatal)
  126. {
  127. throw new \PhpErrorException($message, $severity, 0, $filepath, $line);
  128. }
  129. else
  130. {
  131. $e = new \PhpErrorException($message, $severity, 0, $filepath, $line);
  132. $e->handle();
  133. }
  134. }
  135. return true;
  136. }
  137. /**
  138. * Shows an error. It will stop script execution if the error code is not
  139. * in the errors.continue_on whitelist.
  140. *
  141. * @param Exception $e the exception to show
  142. * @return void
  143. */
  144. public static function show_php_error(\Exception $e)
  145. {
  146. $fatal = (bool)( ! in_array($e->getCode(), \Config::get('errors.continue_on', array())));
  147. $data = static::prepare_exception($e, $fatal);
  148. if ($fatal)
  149. {
  150. $data['contents'] = ob_get_contents();
  151. while (ob_get_level() > 0)
  152. {
  153. ob_end_clean();
  154. }
  155. ob_start(\Config::get('ob_callback', null));
  156. }
  157. else
  158. {
  159. static::$non_fatal_cache[] = $data;
  160. }
  161. if (\Fuel::$is_cli)
  162. {
  163. \Cli::write(\Cli::color($data['severity'].' - '.$data['message'].' in '.\Fuel::clean_path($data['filepath']).' on line '.$data['error_line'], 'red'));
  164. return;
  165. }
  166. if ($fatal)
  167. {
  168. if ( ! headers_sent())
  169. {
  170. $protocol = \Input::server('SERVER_PROTOCOL') ? \Input::server('SERVER_PROTOCOL') : 'HTTP/1.1';
  171. header($protocol.' 500 Internal Server Error');
  172. }
  173. $data['non_fatal'] = static::$non_fatal_cache;
  174. try
  175. {
  176. exit(\View::forge('errors'.DS.'php_fatal_error', $data, false));
  177. }
  178. catch (\FuelException $view_exception)
  179. {
  180. exit($data['severity'].' - '.$data['message'].' in '.\Fuel::clean_path($data['filepath']).' on line '.$data['error_line']);
  181. }
  182. }
  183. try
  184. {
  185. echo \View::forge('errors'.DS.'php_error', $data, false);
  186. }
  187. catch (\FuelException $e)
  188. {
  189. echo $e->getMessage().'<br />';
  190. }
  191. }
  192. /**
  193. * Shows a small notice error, only when not in production or when forced.
  194. * This is used by several libraries to notify the developer of certain things.
  195. *
  196. * @param string $msg the message to display
  197. * @param bool $always_show whether to force display the notice or not
  198. * @return void
  199. */
  200. public static function notice($msg, $always_show = false)
  201. {
  202. $trace = array_merge(array('file' => '(unknown)', 'line' => '(unknown)'), \Arr::get(debug_backtrace(), 1));
  203. logger(\Fuel::L_DEBUG, 'Notice - '.$msg.' in '.$trace['file'].' on line '.$trace['line']);
  204. if (\Fuel::$is_test or ( ! $always_show and (\Fuel::$env == \Fuel::PRODUCTION or \Config::get('errors.notices', true) === false)))
  205. {
  206. return;
  207. }
  208. $data['message'] = $msg;
  209. $data['type'] = 'Notice';
  210. $data['filepath'] = \Fuel::clean_path($trace['file']);
  211. $data['line'] = $trace['line'];
  212. $data['function'] = $trace['function'];
  213. echo \View::forge('errors'.DS.'php_short', $data, false);
  214. }
  215. /**
  216. * Shows the errors/production view and exits. This only gets
  217. * called when an error occurs in production mode.
  218. *
  219. * @return void
  220. */
  221. public static function show_production_error(\Exception $e)
  222. {
  223. // when we're on CLI, always show the php error
  224. if (\Fuel::$is_cli)
  225. {
  226. return static::show_php_error($e);
  227. }
  228. if ( ! headers_sent())
  229. {
  230. $protocol = \Input::server('SERVER_PROTOCOL') ? \Input::server('SERVER_PROTOCOL') : 'HTTP/1.1';
  231. header($protocol.' 500 Internal Server Error');
  232. }
  233. exit(\View::forge('errors'.DS.'production'));
  234. }
  235. protected static function prepare_exception(\Exception $e, $fatal = true)
  236. {
  237. $data = array();
  238. $data['type'] = get_class($e);
  239. $data['severity'] = $e->getCode();
  240. $data['message'] = $e->getMessage();
  241. $data['filepath'] = $e->getFile();
  242. $data['error_line'] = $e->getLine();
  243. $data['backtrace'] = $e->getTrace();
  244. $data['severity'] = ( ! isset(static::$levels[$data['severity']])) ? $data['severity'] : static::$levels[$data['severity']];
  245. foreach ($data['backtrace'] as $key => $trace)
  246. {
  247. if ( ! isset($trace['file']))
  248. {
  249. unset($data['backtrace'][$key]);
  250. }
  251. elseif ($trace['file'] == COREPATH.'classes/error.php')
  252. {
  253. unset($data['backtrace'][$key]);
  254. }
  255. }
  256. $data['debug_lines'] = \Debug::file_lines($data['filepath'], $data['error_line'], $fatal);
  257. $data['orig_filepath'] = $data['filepath'];
  258. $data['filepath'] = \Fuel::clean_path($data['filepath']);
  259. $data['filepath'] = str_replace("\\", "/", $data['filepath']);
  260. return $data;
  261. }
  262. }