BaseErrorHandler.php 10 KB

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