ErrorHandlerMiddleware.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (https://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. (https://cakefoundation.org)
  11. * @link https://cakephp.org CakePHP(tm) Project
  12. * @since 3.3.0
  13. * @license https://opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Error\Middleware;
  16. use Cake\Core\App;
  17. use Cake\Core\Configure;
  18. use Cake\Core\Exception\Exception as CakeException;
  19. use Cake\Core\InstanceConfigTrait;
  20. use Cake\Error\ExceptionRenderer;
  21. use Cake\Error\PHP7ErrorException;
  22. use Cake\Log\Log;
  23. use Error;
  24. use Exception;
  25. use Throwable;
  26. /**
  27. * Error handling middleware.
  28. *
  29. * Traps exceptions and converts them into HTML or content-type appropriate
  30. * error pages using the CakePHP ExceptionRenderer.
  31. *
  32. * @mixin \Cake\Core\InstanceConfigTrait
  33. */
  34. class ErrorHandlerMiddleware
  35. {
  36. use InstanceConfigTrait;
  37. /**
  38. * Default configuration values.
  39. *
  40. * - `log` Enable logging of exceptions.
  41. * - `skipLog` List of exceptions to skip logging. Exceptions that
  42. * extend one of the listed exceptions will also not be logged. Example:
  43. *
  44. * ```
  45. * 'skipLog' => ['Cake\Error\NotFoundException', 'Cake\Error\UnauthorizedException']
  46. * ```
  47. *
  48. * - `trace` Should error logs include stack traces?
  49. *
  50. * @var array
  51. */
  52. protected $_defaultConfig = [
  53. 'skipLog' => [],
  54. 'log' => true,
  55. 'trace' => false,
  56. ];
  57. /**
  58. * Exception render.
  59. *
  60. * @var \Cake\Error\ExceptionRendererInterface|callable|string|null
  61. */
  62. protected $exceptionRenderer;
  63. /**
  64. * Constructor
  65. *
  66. * @param string|callable|null $exceptionRenderer The renderer or class name
  67. * to use or a callable factory. If null, Configure::read('Error.exceptionRenderer')
  68. * will be used.
  69. * @param array $config Configuration options to use. If empty, `Configure::read('Error')`
  70. * will be used.
  71. */
  72. public function __construct($exceptionRenderer = null, array $config = [])
  73. {
  74. if ($exceptionRenderer) {
  75. $this->exceptionRenderer = $exceptionRenderer;
  76. }
  77. $config = $config ?: Configure::read('Error');
  78. $this->setConfig($config);
  79. }
  80. /**
  81. * Wrap the remaining middleware with error handling.
  82. *
  83. * @param \Psr\Http\Message\ServerRequestInterface $request The request.
  84. * @param \Psr\Http\Message\ResponseInterface $response The response.
  85. * @param callable $next Callback to invoke the next middleware.
  86. * @return \Psr\Http\Message\ResponseInterface A response
  87. */
  88. public function __invoke($request, $response, $next)
  89. {
  90. try {
  91. return $next($request, $response);
  92. } catch (Throwable $exception) {
  93. return $this->handleException($exception, $request, $response);
  94. } catch (Exception $exception) {
  95. return $this->handleException($exception, $request, $response);
  96. }
  97. }
  98. /**
  99. * Handle an exception and generate an error response
  100. *
  101. * @param \Exception $exception The exception to handle.
  102. * @param \Psr\Http\Message\ServerRequestInterface $request The request.
  103. * @param \Psr\Http\Message\ResponseInterface $response The response.
  104. * @return \Psr\Http\Message\ResponseInterface A response
  105. */
  106. public function handleException($exception, $request, $response)
  107. {
  108. $renderer = $this->getRenderer($exception, $request);
  109. try {
  110. $res = $renderer->render();
  111. $this->logException($request, $exception);
  112. return $res;
  113. } catch (Throwable $exception) {
  114. $this->logException($request, $exception);
  115. $response = $this->handleInternalError($response);
  116. } catch (Exception $exception) {
  117. $this->logException($request, $exception);
  118. $response = $this->handleInternalError($response);
  119. }
  120. return $response;
  121. }
  122. /**
  123. * @param \Psr\Http\Message\ResponseInterface $response The response
  124. *
  125. * @return \Psr\Http\Message\ResponseInterface A response
  126. */
  127. protected function handleInternalError($response)
  128. {
  129. $body = $response->getBody();
  130. $body->write('An Internal Server Error Occurred');
  131. return $response->withStatus(500)
  132. ->withBody($body);
  133. }
  134. /**
  135. * Get a renderer instance
  136. *
  137. * @param \Exception $exception The exception being rendered.
  138. * @param \Psr\Http\Message\ServerRequestInterface $request The request.
  139. * @return \Cake\Error\ExceptionRendererInterface The exception renderer.
  140. * @throws \Exception When the renderer class cannot be found.
  141. */
  142. protected function getRenderer($exception, $request)
  143. {
  144. if (!$this->exceptionRenderer) {
  145. $this->exceptionRenderer = $this->getConfig('exceptionRenderer') ?: ExceptionRenderer::class;
  146. }
  147. // For PHP5 backwards compatibility
  148. if ($exception instanceof Error) {
  149. $exception = new PHP7ErrorException($exception);
  150. }
  151. if (is_string($this->exceptionRenderer)) {
  152. $class = App::className($this->exceptionRenderer, 'Error');
  153. if (!$class) {
  154. throw new Exception(sprintf(
  155. "The '%s' renderer class could not be found.",
  156. $this->exceptionRenderer
  157. ));
  158. }
  159. return new $class($exception, $request);
  160. }
  161. $factory = $this->exceptionRenderer;
  162. return $factory($exception, $request);
  163. }
  164. /**
  165. * Log an error for the exception if applicable.
  166. *
  167. * @param \Psr\Http\Message\ServerRequestInterface $request The current request.
  168. * @param \Exception $exception The exception to log a message for.
  169. * @return void
  170. */
  171. protected function logException($request, $exception)
  172. {
  173. if (!$this->getConfig('log')) {
  174. return;
  175. }
  176. foreach ((array)$this->getConfig('skipLog') as $class) {
  177. if ($exception instanceof $class) {
  178. return;
  179. }
  180. }
  181. Log::error($this->getMessage($request, $exception));
  182. }
  183. /**
  184. * Generate the error log message.
  185. *
  186. * @param \Psr\Http\Message\ServerRequestInterface $request The current request.
  187. * @param \Exception $exception The exception to log a message for.
  188. * @return string Error message
  189. */
  190. protected function getMessage($request, $exception)
  191. {
  192. $message = sprintf(
  193. '[%s] %s',
  194. get_class($exception),
  195. $exception->getMessage()
  196. );
  197. $debug = Configure::read('debug');
  198. if ($debug && $exception instanceof CakeException) {
  199. $attributes = $exception->getAttributes();
  200. if ($attributes) {
  201. $message .= "\nException Attributes: " . var_export($exception->getAttributes(), true);
  202. }
  203. }
  204. $message .= "\nRequest URL: " . $request->getRequestTarget();
  205. $referer = $request->getHeaderLine('Referer');
  206. if ($referer) {
  207. $message .= "\nReferer URL: " . $referer;
  208. }
  209. if ($this->getConfig('trace')) {
  210. $message .= "\nStack Trace:\n" . $exception->getTraceAsString() . "\n\n";
  211. }
  212. return $message;
  213. }
  214. }