1: <?php
2: /**
3: * ErrorHandler class
4: *
5: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
6: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
7: *
8: * Licensed under The MIT License
9: * For full copyright and license information, please see the LICENSE.txt
10: * Redistributions of files must retain the above copyright notice.
11: *
12: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
13: * @link https://cakephp.org CakePHP(tm) Project
14: * @since 0.10.5
15: * @license https://opensource.org/licenses/mit-license.php MIT License
16: */
17: namespace Cake\Error;
18:
19: use Cake\Core\App;
20: use Cake\Http\ResponseEmitter;
21: use Exception;
22: use Throwable;
23:
24: /**
25: * Error Handler provides basic error and exception handling for your application. It captures and
26: * handles all unhandled exceptions and errors. Displays helpful framework errors when debug mode is on.
27: *
28: * ### Uncaught exceptions
29: *
30: * When debug mode is off a ExceptionRenderer will render 404 or 500 errors. If an uncaught exception is thrown
31: * and it is a type that ExceptionRenderer does not know about it will be treated as a 500 error.
32: *
33: * ### Implementing application specific exception handling
34: *
35: * You can implement application specific exception handling in one of a few ways. Each approach
36: * gives you different amounts of control over the exception handling process.
37: *
38: * - Modify config/error.php and setup custom exception handling.
39: * - Use the `exceptionRenderer` option to inject an Exception renderer. This will
40: * let you keep the existing handling logic but override the rendering logic.
41: *
42: * #### Create your own Exception handler
43: *
44: * This gives you full control over the exception handling process. The class you choose should be
45: * loaded in your config/error.php and registered as the default exception handler.
46: *
47: * #### Using a custom renderer with `exceptionRenderer`
48: *
49: * If you don't want to take control of the exception handling, but want to change how exceptions are
50: * rendered you can use `exceptionRenderer` option to choose a class to render exception pages. By default
51: * `Cake\Error\ExceptionRenderer` is used. Your custom exception renderer class should be placed in src/Error.
52: *
53: * Your custom renderer should expect an exception in its constructor, and implement a render method.
54: * Failing to do so will cause additional errors.
55: *
56: * #### Logging exceptions
57: *
58: * Using the built-in exception handling, you can log all the exceptions
59: * that are dealt with by ErrorHandler by setting `log` option to true in your config/error.php.
60: * Enabling this will log every exception to Log and the configured loggers.
61: *
62: * ### PHP errors
63: *
64: * Error handler also provides the built in features for handling php errors (trigger_error).
65: * While in debug mode, errors will be output to the screen using debugger. While in production mode,
66: * errors will be logged to Log. You can control which errors are logged by setting
67: * `errorLevel` option in config/error.php.
68: *
69: * #### Logging errors
70: *
71: * When ErrorHandler is used for handling errors, you can enable error logging by setting the `log`
72: * option to true. This will log all errors to the configured log handlers.
73: *
74: * #### Controlling what errors are logged/displayed
75: *
76: * You can control which errors are logged / displayed by ErrorHandler by setting `errorLevel`. Setting this
77: * to one or a combination of a few of the E_* constants will only enable the specified errors:
78: *
79: * ```
80: * $options['errorLevel'] = E_ALL & ~E_NOTICE;
81: * ```
82: *
83: * Would enable handling for all non Notice errors.
84: *
85: * @see \Cake\Error\ExceptionRenderer for more information on how to customize exception rendering.
86: */
87: class ErrorHandler extends BaseErrorHandler
88: {
89: /**
90: * Constructor
91: *
92: * @param array $options The options for error handling.
93: */
94: public function __construct($options = [])
95: {
96: $defaults = [
97: 'log' => true,
98: 'trace' => false,
99: 'exceptionRenderer' => ExceptionRenderer::class,
100: ];
101: $this->_options = $options + $defaults;
102: }
103:
104: /**
105: * Display an error.
106: *
107: * Template method of BaseErrorHandler.
108: *
109: * @param array $error An array of error data.
110: * @param bool $debug Whether or not the app is in debug mode.
111: * @return void
112: */
113: protected function _displayError($error, $debug)
114: {
115: if (!$debug) {
116: return;
117: }
118: Debugger::getInstance()->outputError($error);
119: }
120:
121: /**
122: * Displays an exception response body.
123: *
124: * @param \Exception $exception The exception to display.
125: * @return void
126: * @throws \Exception When the chosen exception renderer is invalid.
127: */
128: protected function _displayException($exception)
129: {
130: $rendererClassName = App::className($this->_options['exceptionRenderer'], 'Error');
131: try {
132: if (!$rendererClassName) {
133: throw new Exception("$rendererClassName is an invalid class.");
134: }
135: /** @var \Cake\Error\ExceptionRendererInterface $renderer */
136: $renderer = new $rendererClassName($exception);
137: $response = $renderer->render();
138: $this->_clearOutput();
139: $this->_sendResponse($response);
140: } catch (Throwable $exception) {
141: $this->_logInternalError($exception);
142: } catch (Exception $exception) {
143: $this->_logInternalError($exception);
144: }
145: }
146:
147: /**
148: * Clear output buffers so error pages display properly.
149: *
150: * Easily stubbed in testing.
151: *
152: * @return void
153: */
154: protected function _clearOutput()
155: {
156: while (ob_get_level()) {
157: ob_end_clean();
158: }
159: }
160:
161: /**
162: * Logs both PHP5 and PHP7 errors.
163: *
164: * The PHP5 part will be removed with 4.0.
165: *
166: * @param \Throwable|\Exception $exception Exception.
167: *
168: * @return void
169: */
170: protected function _logInternalError($exception)
171: {
172: // Disable trace for internal errors.
173: $this->_options['trace'] = false;
174: $message = sprintf(
175: "[%s] %s\n%s", // Keeping same message format
176: get_class($exception),
177: $exception->getMessage(),
178: $exception->getTraceAsString()
179: );
180: trigger_error($message, E_USER_ERROR);
181: }
182:
183: /**
184: * Method that can be easily stubbed in testing.
185: *
186: * @param string|\Cake\Http\Response $response Either the message or response object.
187: * @return void
188: */
189: protected function _sendResponse($response)
190: {
191: if (is_string($response)) {
192: echo $response;
193:
194: return;
195: }
196:
197: $emitter = new ResponseEmitter();
198: $emitter->emit($response);
199: }
200: }
201: