While searching for ready exception handler I did not found any that could match following requirements:
1) Handle Exception and any other (AppException, MyAppException etc) exception that are Exception super class;
2) Send email notification about exception;
3) Dump exception to log;
4) Show nice page to the user:
a) A custom landing page can be defined for exception;
b) For all other exceptions a default landing page should be used;
5) Landing page text should be localizable;
As a good start I’ve used existing AppException implementation [1]. It matches requirements: 4 and 5, but can not handle Exceptions, only AppException. So, here my evolved version of the code:
<?php /** * Aplication wide exception handler class. * * Installation: * 1) Copy file in to the app directory; * 2) Add to bootstrap.ctp folowing lines: * require_once(APP.'app_exception.php'); * set_exception_handler(array('AppExceptionHandler', 'handleException')); * 3) Add default view to render exceptions in /exceptions/unknown.ctp, * view has access to $info variable * * References: * http://www.mt-soft.com.ar/2007/12/21/handling-exceptions-in-cakephp-12/ */ class AppExceptionHandler extends Object { static function handleException($exception) { $parsed = new AppExceptionParser($exception); $instance = new AppExceptionHandler(); $instance->renderException($parsed->getInfo()); $instance->logException($parsed->getInfo()); $instance->emailException($parsed); exit; } function renderException($info = array()) { $Controller = new Controller(); $Controller->viewPath = 'exceptions'; $Controller->layout = 'exception'; $Dispatcher = new Dispatcher(); $Controller->base = $Dispatcher->baseUrl(); $Controller->webroot = $Dispatcher->webroot; $Controller->set(compact('info')); $View = new View($Controller); $view = @$info['type']; if (!file_exists(VIEWS.'exceptions'.DS.$view.'.ctp')) { $view = 'unknown'; } //header('HTTP/1.0 500 Internal Server Error'); $out = $View->render($view); $Controller->afterFilter(); echo $out; } function logException($info) { $this->log(serialize($info), LOG_ERROR); } function emailException($message) { App::import('Core', 'Email'); $email = new EmailComponent; $email->from = 'noreply@example.com'; $email->to = 'dev@example.com'; $email->sendAs = 'text'; $email->subject = 'Exception notification'; $email->send($message); } } /** * Application exception info synthetic class. */ class AppExceptionParser extends Object { var $exception = null; function __construct($exception) { $this->exception = $exception; } public function __toString() { return print_r($this->getInfo(), true); } function getInfo() { return array_merge( array( 'type' => $this->getType(), 'message' => $this->getMessage() ), $this->where(), $this->context() ); } function getType() { if (get_class($this->exception) == 'AppException' || is_subclass_of($this->exception, 'AppException')) { return $this->exception->getType(); } else return 'unknown'; } function getMessage() { return $this->exception->getMessage(); } function where() { return array( 'function' => $this->getClass().'::'.$this->getFunction(), 'file' => $this->exception->getFile(), 'line' => $this->exception->getLine(), 'url' => $this->getUrl() ); } function getUrl($full = true) { return Router::url(array('full_base' => $full)); } function getClass() { $trace = $this->exception->getTrace(); return $trace[0]['class']; } function getFunction() { $trace = $this->exception->getTrace(); return $trace[0]['function']; } function context() { return array( 'remoteAddr' => $this->getRemoteAddr(), 'requestMethod' => $this->getRequestMethod(), 'httpUserAgent' => $this->getHttpUserAgent(), 'httpAcceptLangage' => $this->getHttpAcceptLanguage(), 'httpReferer' => $this->getHttpReferer(), 'sessionAuth' => $this->getSessionAuth() ); } function getRemoteAddr() { return $_SERVER['REMOTE_ADDR']; } function getRequestMethod() { return $_SERVER['REQUEST_METHOD']; } function getHttpUserAgent() { return $_SERVER['HTTP_USER_AGENT']; } function getHttpAcceptLanguage() { return $_SERVER['HTTP_ACCEPT_LANGUAGE']; } function getHttpReferer() { return isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : 'None'; } function getSessionAuth() { return isset($_SESSION['Auth']) ? print_r($_SESSION['Auth'], true) : 'Anonymous'; } } /** * Application base exception class. The $info['type'] stores name of the view file in /views/exceptions/, without .ctp. * Default view is /views/exceptions/unknon.ctp */ class AppException extends Exception { var $type = null; function __construct($message, $type = 'unknown') { parent::__construct($message); $this->type = $type; } function getType() { return $this->type; } } ?>
To attach AppExceptionHandler to yours the CakePHP application add to the bootstrap.php following lines:
require_once(APP.'app_exception.php'); if (configure::read() <= 1) { set_exception_handler(array('AppExceptionHandler', 'handleException')); }
The last step is to create /views/exceptions/unknown.ctp, here is my example:
<div> <p><img src="/img/logo.png"/></p> <h1><?php __('-views-exceptions-unknown-header') ?></h1> <p><?php __('-views-exceptions-unknown-text-short_term'); ?></p> <p><?php __('-views-exceptions-unknown-text-long_term'); ?></p> <div id="debug" class="cake-exception-log"> <table><?php if (Configure::read() >= 1) { foreach ($info as $name => $value) { echo '<tr><th>'.$name.':</th><td id="'.$name.'">'.$value.'</td></tr>'; } } ?></table> </div> </div>
That’s it. Hope this helps.
References:
[1] http://www.mt-soft.com.ar/2007/12/21/handling-exceptions-in-cakephp-12/
October 21st, 2009 at 3:00 pm
Excellent!
This was just what I was looking for! Great job!
Thank you,
Best regards.