BaseErrorHandler.php 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 2.0.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Error;
  16. use Cake\Core\Configure;
  17. use Cake\Error\Debugger;
  18. use Cake\Log\Log;
  19. use Cake\Network\Exception\InternalErrorException;
  20. use Cake\Routing\Router;
  21. /**
  22. * Base error handler that provides logic common to the CLI + web
  23. * error/exception handlers.
  24. *
  25. * Subclasses are required to implement the template methods to handle displaying
  26. * the errors in their environment.
  27. */
  28. abstract class BaseErrorHandler {
  29. /**
  30. * Display an error message in an environment specific way.
  31. *
  32. * Subclasses should implement this method to display the error as
  33. * desired for the runtime they operate in.
  34. *
  35. * @param array $error An array of error data.
  36. * @param bool $debug Whether or not the app is in debug mode.
  37. * @return void
  38. */
  39. abstract protected function _displayError($error, $debug);
  40. /**
  41. * Display an exception in an environment specific way.
  42. *
  43. * Subclasses should implement this method to display an uncaught exception as
  44. * desired for the runtime they operate in.
  45. *
  46. * @param \Exception $exception The uncaught exception.
  47. * @return void
  48. */
  49. abstract protected function _displayException($exception);
  50. /**
  51. * Register the error and exception handlers.
  52. *
  53. * @return void
  54. */
  55. public function register() {
  56. $level = -1;
  57. if (isset($this->_options['errorLevel'])) {
  58. $level = $this->_options['errorLevel'];
  59. }
  60. error_reporting($level);
  61. set_error_handler([$this, 'handleError'], $level);
  62. set_exception_handler([$this, 'handleException']);
  63. register_shutdown_function(function () {
  64. if (php_sapi_name() === 'cli') {
  65. return;
  66. }
  67. $error = error_get_last();
  68. if (!is_array($error)) {
  69. return;
  70. }
  71. $fatals = [
  72. E_USER_ERROR,
  73. E_ERROR,
  74. E_PARSE,
  75. ];
  76. if (!in_array($error['type'], $fatals, true)) {
  77. return;
  78. }
  79. $this->handleFatalError(
  80. $error['type'],
  81. $error['message'],
  82. $error['file'],
  83. $error['line']
  84. );
  85. });
  86. }
  87. /**
  88. * Set as the default error handler by CakePHP.
  89. *
  90. * Use config/error.php to customize or replace this error handler.
  91. * This function will use Debugger to display errors when debug > 0. And
  92. * will log errors to Log, when debug == 0.
  93. *
  94. * You can use the 'errorLevel' option to set what type of errors will be handled.
  95. * Stack traces for errors can be enabled with the 'trace' option.
  96. *
  97. * @param int $code Code of error
  98. * @param string $description Error description
  99. * @param string $file File on which error occurred
  100. * @param int $line Line that triggered the error
  101. * @param array $context Context
  102. * @return bool true if error was handled
  103. */
  104. public function handleError($code, $description, $file = null, $line = null, $context = null) {
  105. if (error_reporting() === 0) {
  106. return false;
  107. }
  108. list($error, $log) = $this->mapErrorCode($code);
  109. if ($log === LOG_ERR) {
  110. return $this->handleFatalError($code, $description, $file, $line);
  111. }
  112. $data = array(
  113. 'level' => $log,
  114. 'code' => $code,
  115. 'error' => $error,
  116. 'description' => $description,
  117. 'file' => $file,
  118. 'line' => $line,
  119. );
  120. $debug = Configure::read('debug');
  121. if ($debug) {
  122. $data += [
  123. 'context' => $context,
  124. 'start' => 3,
  125. 'path' => Debugger::trimPath($file)
  126. ];
  127. }
  128. $this->_displayError($data, $debug);
  129. $this->_logError($log, $data);
  130. return true;
  131. }
  132. /**
  133. * Handle uncaught exceptions.
  134. *
  135. * Uses a template method provided by subclasses to display errors in an
  136. * environment appropriate way.
  137. *
  138. * @param \Exception $exception Exception instance.
  139. * @return void
  140. * @throws Exception When renderer class not found
  141. * @see http://php.net/manual/en/function.set-exception-handler.php
  142. */
  143. public function handleException(\Exception $exception) {
  144. $this->_displayException($exception);
  145. $this->_logException($exception);
  146. $this->_stop($exception->getCode() ?: 1);
  147. }
  148. /**
  149. * Stop the process.
  150. *
  151. * Implemented in subclasses that need it.
  152. *
  153. * @param int $code Exit code.
  154. * @return void
  155. */
  156. protected function _stop($code) {
  157. // Do nothing.
  158. }
  159. /**
  160. * Display/Log a fatal error.
  161. *
  162. * @param int $code Code of error
  163. * @param string $description Error description
  164. * @param string $file File on which error occurred
  165. * @param int $line Line that triggered the error
  166. * @return bool
  167. */
  168. public function handleFatalError($code, $description, $file, $line) {
  169. $data = [
  170. 'code' => $code,
  171. 'description' => $description,
  172. 'file' => $file,
  173. 'line' => $line,
  174. 'error' => 'Fatal Error',
  175. ];
  176. $this->_logError(LOG_ERR, $data);
  177. $this->handleException(new FatalErrorException($description, 500, $file, $line));
  178. return true;
  179. }
  180. /**
  181. * Log an error.
  182. *
  183. * @param string $level The level name of the log.
  184. * @param array $data Array of error data.
  185. * @return void
  186. */
  187. protected function _logError($level, $data) {
  188. $message = sprintf('%s (%s): %s in [%s, line %s]',
  189. $data['error'],
  190. $data['code'],
  191. $data['description'],
  192. $data['file'],
  193. $data['line']
  194. );
  195. if (!empty($this->_options['trace'])) {
  196. $trace = Debugger::trace(array(
  197. 'start' => 1,
  198. 'format' => 'log'
  199. ));
  200. $message .= "\nTrace:\n" . $trace . "\n";
  201. }
  202. $message .= "\n\n";
  203. return Log::write($level, $message);
  204. }
  205. /**
  206. * Handles exception logging
  207. *
  208. * @param Exception $exception Exception instance.
  209. * @return bool
  210. */
  211. protected function _logException(\Exception $exception) {
  212. $config = $this->_options;
  213. if (empty($config['log'])) {
  214. return false;
  215. }
  216. if (!empty($config['skipLog'])) {
  217. foreach ((array)$config['skipLog'] as $class) {
  218. if ($exception instanceof $class) {
  219. return false;
  220. }
  221. }
  222. }
  223. return Log::error($this->_getMessage($exception));
  224. }
  225. /**
  226. * Generates a formatted error message
  227. *
  228. * @param Exception $exception Exception instance
  229. * @return string Formatted message
  230. */
  231. protected function _getMessage(\Exception $exception) {
  232. $message = sprintf("[%s] %s",
  233. get_class($exception),
  234. $exception->getMessage()
  235. );
  236. if (method_exists($exception, 'getAttributes')) {
  237. $attributes = $exception->getAttributes();
  238. if ($attributes) {
  239. $message .= "\nException Attributes: " . var_export($exception->getAttributes(), true);
  240. }
  241. }
  242. if (php_sapi_name() !== 'cli') {
  243. $request = Router::getRequest();
  244. if ($request) {
  245. $message .= "\nRequest URL: " . $request->here();
  246. }
  247. }
  248. $message .= "\nStack Trace:\n" . $exception->getTraceAsString() . "\n\n";
  249. return $message;
  250. }
  251. /**
  252. * Map an error code into an Error word, and log location.
  253. *
  254. * @param int $code Error code to map
  255. * @return array Array of error word, and log location.
  256. */
  257. public static function mapErrorCode($code) {
  258. $levelMap = [
  259. E_PARSE => 'error',
  260. E_ERROR => 'error',
  261. E_CORE_ERROR => 'error',
  262. E_COMPILE_ERROR => 'error',
  263. E_USER_ERROR => 'error',
  264. E_WARNING => 'warning',
  265. E_USER_WARNING => 'warning',
  266. E_COMPILE_WARNING => 'warning',
  267. E_RECOVERABLE_ERROR => 'warning',
  268. E_NOTICE => 'notice',
  269. E_USER_NOTICE => 'notice',
  270. E_STRICT => 'strict',
  271. E_DEPRECATED => 'deprecated',
  272. E_USER_DEPRECATED => 'deprecated',
  273. ];
  274. $logMap = [
  275. 'error' => LOG_ERR,
  276. 'warning' => LOG_WARNING,
  277. 'notice' => LOG_NOTICE,
  278. 'strict' => LOG_NOTICE,
  279. 'deprecated' => LOG_NOTICE,
  280. ];
  281. $error = $levelMap[$code];
  282. $log = $logMap[$error];
  283. return [ucfirst($error), $log];
  284. }
  285. }