ExceptionRenderer.php 8.3 KB

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