ExceptionRenderer.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  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\Controller\Controller;
  17. use Cake\Controller\ErrorController;
  18. use Cake\Core\Configure;
  19. use Cake\Core\Exception\Exception as CakeException;
  20. use Cake\Core\Exception\MissingPluginException;
  21. use Cake\Event\Event;
  22. use Cake\Network\Exception\HttpException;
  23. use Cake\Network\Request;
  24. use Cake\Network\Response;
  25. use Cake\Routing\Router;
  26. use Cake\Utility\Inflector;
  27. use Cake\View\Exception\MissingViewException;
  28. use Exception;
  29. /**
  30. * Exception Renderer.
  31. *
  32. * Captures and handles all unhandled exceptions. Displays helpful framework errors when debug is true.
  33. * When debug is false a CakeException will render 404 or 500 errors. If an uncaught exception is thrown
  34. * and it is a type that ExceptionHandler does not know about it will be treated as a 500 error.
  35. *
  36. * ### Implementing application specific exception rendering
  37. *
  38. * You can implement application specific exception handling by creating a subclass of
  39. * ExceptionRenderer and configure it to be the `exceptionRenderer` in config/error.php
  40. *
  41. * #### Using a subclass of ExceptionRenderer
  42. *
  43. * Using a subclass of ExceptionRenderer gives you full control over how Exceptions are rendered, you
  44. * can configure your class in your config/app.php.
  45. */
  46. class ExceptionRenderer {
  47. /**
  48. * Controller instance.
  49. *
  50. * @var Controller
  51. */
  52. public $controller = null;
  53. /**
  54. * Template to render for Cake\Core\Exception\Exception
  55. *
  56. * @var string
  57. */
  58. public $template = '';
  59. /**
  60. * The method corresponding to the Exception this object is for.
  61. *
  62. * @var string
  63. */
  64. public $method = '';
  65. /**
  66. * The exception being handled.
  67. *
  68. * @var \Exception
  69. */
  70. public $error = null;
  71. /**
  72. * Creates the controller to perform rendering on the error response.
  73. * If the error is a Cake\Core\Exception\Exception it will be converted to either a 400 or a 500
  74. * code error depending on the code used to construct the error.
  75. *
  76. * @param \Exception $exception Exception
  77. */
  78. public function __construct(Exception $exception) {
  79. $this->error = $exception;
  80. $this->controller = $this->_getController();
  81. }
  82. /**
  83. * Get the controller instance to handle the exception.
  84. * Override this method in subclasses to customize the controller used.
  85. * This method returns the built in `ErrorController` normally, or if an error is repeated
  86. * a bare controller will be used.
  87. *
  88. * @return \Cake\Controller\Controller
  89. */
  90. protected function _getController() {
  91. if (!$request = Router::getRequest(true)) {
  92. $request = Request::createFromGlobals();
  93. }
  94. $response = new Response();
  95. try {
  96. $controller = new ErrorController($request, $response);
  97. $controller->startupProcess();
  98. } catch (Exception $e) {
  99. if (!empty($controller) && isset($controller->RequestHandler)) {
  100. $event = new Event('Controller.startup', $controller);
  101. $controller->RequestHandler->startup($event);
  102. }
  103. }
  104. if (empty($controller)) {
  105. $controller = new Controller($request, $response);
  106. $controller->viewPath = 'Error';
  107. }
  108. return $controller;
  109. }
  110. /**
  111. * Renders the response for the exception.
  112. *
  113. * @return Cake\Network\Response The response to be sent.
  114. */
  115. public function render() {
  116. $exception = $this->error;
  117. $code = $this->_code($exception);
  118. $method = $this->_method($exception);
  119. $template = $this->_template($exception, $method, $code);
  120. $isDebug = Configure::read('debug');
  121. if (($isDebug || $exception instanceof HttpException) &&
  122. method_exists($this, $method)
  123. ) {
  124. return $this->_customMethod($method, $exception);
  125. }
  126. $message = $this->_message($exception, $code);
  127. $url = $this->controller->request->here();
  128. if (method_exists($exception, 'responseHeader')) {
  129. $this->controller->response->header($exception->responseHeader());
  130. }
  131. $this->controller->response->statusCode($code);
  132. $this->controller->set(array(
  133. 'message' => h($message),
  134. 'url' => h($url),
  135. 'error' => $exception,
  136. 'code' => $code,
  137. '_serialize' => array('message', 'url', 'code')
  138. ));
  139. if ($exception instanceof CakeException && $isDebug) {
  140. $this->controller->set($this->error->getAttributes());
  141. }
  142. return $this->_outputMessage($template);
  143. }
  144. /**
  145. * Render a custom error method/template.
  146. *
  147. * @param string $method The method name to invoke.
  148. * @param \Exception $exception The exception to render.
  149. * @return \Cake\Network\Response The response to send.
  150. */
  151. protected function _customMethod($method, $exception) {
  152. $result = call_user_func([$this, $method], $exception);
  153. if (is_string($result)) {
  154. $this->controller->response->body($result);
  155. $result = $this->controller->response;
  156. }
  157. return $result;
  158. }
  159. /**
  160. * Get method name
  161. *
  162. * @param \Exception $exception Exception instance.
  163. * @return string
  164. */
  165. protected function _method(\Exception $exception) {
  166. list(, $baseClass) = namespaceSplit(get_class($exception));
  167. $baseClass = substr($baseClass, 0, -9);
  168. $method = Inflector::variable($baseClass) ?: 'error500';
  169. return $this->method = $method;
  170. }
  171. /**
  172. * Get error message.
  173. *
  174. * @param \Exception $exception Exception
  175. * @param int $code Error code
  176. * @return string Error message
  177. */
  178. protected function _message(\Exception $exception, $code) {
  179. $message = $this->error->getMessage();
  180. if (!Configure::read('debug') &&
  181. !($exception instanceof HttpException)
  182. ) {
  183. if ($code < 500) {
  184. $message = __d('cake', 'Not Found');
  185. } else {
  186. $message = __d('cake', 'An Internal Error Has Occurred.');
  187. }
  188. }
  189. return $message;
  190. }
  191. /**
  192. * Get template for rendering exception info.
  193. *
  194. * @param \Exception $exception Exception instance.
  195. * @param string $method Method name
  196. * @param int $code Error code
  197. * @return string Template name
  198. */
  199. protected function _template(\Exception $exception, $method, $code) {
  200. $isHttpException = $exception instanceof HttpException;
  201. if (!Configure::read('debug') && !$isHttpException) {
  202. $template = 'error500';
  203. if ($code < 500) {
  204. $template = 'error400';
  205. }
  206. return $this->template = $template;
  207. }
  208. if ($isHttpException) {
  209. $template = 'error500';
  210. if ($code < 500) {
  211. $template = 'error400';
  212. }
  213. return $this->template = $template;
  214. }
  215. $template = $method ?: 'error500';
  216. if ($exception instanceof \PDOException) {
  217. $template = 'pdo_error';
  218. }
  219. return $this->template = $template;
  220. }
  221. /**
  222. * Get an error code value within range 400 to 506
  223. *
  224. * @param \Exception $exception Exception
  225. * @return int Error code value within range 400 to 506
  226. */
  227. protected function _code(\Exception $exception) {
  228. $code = 500;
  229. $errorCode = $exception->getCode();
  230. if ($errorCode >= 400 && $errorCode < 506) {
  231. $code = $errorCode;
  232. }
  233. return $code;
  234. }
  235. /**
  236. * Generate the response using the controller object.
  237. *
  238. * @param string $template The template to render.
  239. * @return Cake\Network\Response A response object that can be sent.
  240. */
  241. protected function _outputMessage($template) {
  242. try {
  243. $this->controller->render($template);
  244. $event = new Event('Controller.shutdown', $this->controller);
  245. $this->controller->afterFilter($event);
  246. return $this->controller->response;
  247. } catch (MissingViewException $e) {
  248. $attributes = $e->getAttributes();
  249. if (isset($attributes['file']) && strpos($attributes['file'], 'error500') !== false) {
  250. return $this->_outputMessageSafe('error500');
  251. }
  252. return $this->_outputMessage('error500');
  253. } catch (MissingPluginException $e) {
  254. $attributes = $e->getAttributes();
  255. if (isset($attributes['plugin']) && $attributes['plugin'] === $this->controller->plugin) {
  256. $this->controller->plugin = null;
  257. }
  258. return $this->_outputMessageSafe('error500');
  259. } catch (\Exception $e) {
  260. return $this->_outputMessageSafe('error500');
  261. }
  262. }
  263. /**
  264. * A safer way to render error messages, replaces all helpers, with basics
  265. * and doesn't call component methods.
  266. *
  267. * @param string $template The template to render
  268. * @return void
  269. */
  270. protected function _outputMessageSafe($template) {
  271. $this->controller->layoutPath = null;
  272. $this->controller->subDir = null;
  273. $this->controller->viewPath = 'Error';
  274. $this->controller->layout = 'error';
  275. $this->controller->helpers = array('Form', 'Html', 'Session');
  276. $view = $this->controller->createView();
  277. $this->controller->response->body($view->render($template, 'error'));
  278. $this->controller->response->type('html');
  279. return $this->controller->response;
  280. }
  281. }