Throwing and handling exceptions

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.

Getting ready

We need a basic application skeleton to work with. Follow the entire recipe Detecting file types with MagicDb.

How to do it...

  1. Edit your 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');
    
  2. Create a file named 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();
    }
    }
    ?>
    
  3. Create a file named 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();
    }
    }
    
  4. While still editing your 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);
    }
    
  5. Add the following at the end of your app/config/bootstrap.php file (right above the closing PHP tag):
    App::import('Lib', 'ExceptionHandler');
    set_exception_handler(array('ExceptionHandler', 'handleException'));
    
  6. Create a folder named 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:

How to do it...

How it works...

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.218.226