CakePHP 1.3 still offers support for PHP4, yet most CakePHP applications are built exclusively for PHP5. Therefore, it is only expected that our applications use language features only available in PHP5, such as exceptions.
However, there is no built-in support in CakePHP to handle exceptions. This recipe shows us how to create a base exception class that can be used throughout our application, and how to properly recover the application workflow after an exception is thrown.
We need a basic application skeleton to work with. Follow the entire recipe Detecting file types with MagicDb.
app/controllers/uploads_controller.php
file and change the view()
and download()
methods, so that where it reads:$this->cakeError('error404');
It now reads:
throw new AppException('Upload '.$id.' not found');
app_exception.php
and place it in your app/
folder, with the following contents:<?php class AppException extends Exception { public function getInfo() { return array( 'message' => $this->getMessage(), 'trace' => $this->getStackTrace(), 'url' => Router::url(null, true), 'method' => env('REQUEST_METHOD'); 'referer' => env('HTTP_REFERER'); 'POST' => $_POST, 'GET' => $_GET, 'SESSION' => $_SESSION ); } public function getStackTrace($array = true, $count = 5) { if ($array) { $trace = $this->getTrace(); if (!empty($count)) { $trace = array_slice($trace, 0, $count); } foreach($trace as $i => $row) { $location = ''; if (!empty($row['class'])) { $location .= $row['class'] . $row['type'] . $row['function'] . '()'; } $file = !empty($row['file']) ? str_replace(ROOT.DS, '', $row['file']) : ''; if (!empty($file)) { if (!empty($location)) { $location .= ' (' . $file . '@' . $row['line'] . ')'; } else { $location .= $file . '@' . $row['line']; } } $trace[$i]['location'] = $location; unset($trace[$i]['args']); } return $trace; } return $this->getTraceAsString(); } } ?>
exception_handler.php
and place it in your app/libs
folder, with the following contents:<?php App::import(array('type'=>'File', 'name'=>'AppException', 'file'=>APP.'app_exception.php')); App::import('Core', 'Controller'); class ExceptionHandler extends Object { public static function handleException($exception) { self::getInstance(); self::logException($exception); self::renderException($exception); self::_stop(); } }
app/libs/exception_handler.php
file, add the following methods to the ExceptionHandler
class:public function renderException($exception) { $Dispatcher = new Dispatcher(); $Controller = new Controller(); $Controller->params = array( 'controller' => 'exceptions', 'action' => 'exception' ); $Controller->viewPath = 'exceptions'; if (file_exists(VIEWS.'layouts'.DS.'exception.ctp')) { $Controller->layout = 'exception'; } $Controller->base = $Dispatcher->baseUrl(); $Controller->webroot = $Dispatcher->webroot; $Controller->set(compact('exception')); $View = new View($Controller); if (!file_exists(VIEWS.'exceptions'.DS.'view.ctp')) { if (Configure::read('debug') > 0) { echo '<strong>Exception</strong>: '; echo $exception->getMessage(); echo '<pre>'; echo $exception->getStackTrace(false); echo '</pre>'; return; } return $Controller->redirect(null, 500); } echo $View->render('view'); } public function logException($exception) { $trace = $exception->getStackTrace(); $message = get_class($exception) . ' thrown in ' . $trace[0]['location']; $message .= ': ' . $exception->getMessage(); if (is_a($exception, instanceof AppException)) { $message .= ' | DEBUG: ' . json_encodevar_export($exception->getInfo(), true); } self::log($message, LOG_ERROR); }
app/config/bootstrap.php
file (right above the closing PHP tag):App::import('Lib', 'ExceptionHandler'); set_exception_handler(array('ExceptionHandler', 'handleException'));
exceptions
in your app/views
folder. Create a file named view.ctp
and place it in your app/views/exceptions
folder, with the following contents:<h2><?php echo $exception->getMessage(); ?></h2> <?php if (Configure::read('debug') > 0) { ?> <ol> <?php foreach($exception->getStackTrace() as $trace) { ?> <li><?php echo $trace['location']; ?></li> <?php } ?> </ol> <?php if (is_a($exception, 'AppException')) { ?> <?php debug(array_diff_key($exception->getInfo(), array('message'=>null, 'trace'=>null))); ?> <?php } ?> <?php } else { ?> <p>An error has been found. It has been logged, and will soon be fixed.</p> <?php } ?>
If you now force an error by browsing to http://localhost/uploads/view/xx
, you will see a page describing the exception, its stack trace, and including relevant information, such as the URL, any POST or GET parameters, and session information, as shown in the following screenshot:
We start by using exceptions in our UploadsController
class, instead of using CakePHP's cakeError()
method, whenever an Upload
record is not found. These exceptions are actually instances of AppException
, but we could have as well created custom exceptions that inherit from AppException
.
The AppException
class provides us with a base class from where to extend our application exceptions. This class offers us more contextual information through its getInfo()
method. This information includes not only the exception message and the stack trace (which is simplified by removing the arguments, and limiting the number of items), but also the URL, method, any POST or GET data, and session information, details that can become valuable when working out the exception.
We still have to add the ability to handle any exceptions that are thrown. For that purpose, we create the ExceptionHandler
class. Through the code added to the app/config/bootstrap.php
file, which uses PHP's set_exception_handler()
function, we tell PHP that whenever an exception is thrown and not caught anywhere, the static handleException()
method of the ExceptionHandler
class is to be executed.
This method logs the exception, using the logException()
method, and renders a friendly page by calling the renderException()
method. This rendering is performed by creating a dummy controller as an instance of Controller
, using this controller to render the view app/views/exceptions.ctp
(optionally using a layout named exception.ctp
if one is available in app/views/exceptions
), and setting the view variable exception
to the exception being handled.
This view shows a simple message if the debug level is set to 0
, or a thorough description of the stack trace and any context information that may be relevant.
3.149.234.188