Every so often, I wish to both raise an exception to the UI as well as redirect to a specific page on the site. Normally, I’ll have code that looks like the following:

<?php
class PostsController extends AppController {
  public function view($id) {
    try {
      $post = $this->Post->findById($id);
    catch (MissingPost $e) {
      $this->Session->setFlash("No post available");
      return $this->redirect('posts/index');
    } catch (NoPostPermissions $e) {
      $this->Session->setFlash("You can't view this");
      return $this->redirect('users/home');
    } catch (UnapprovedPost $e) {
      return $this->redirect('posts/index');
    } catch (Exception $e) {
      $this->Session->setFlash($e->getMessage());
      return $this->redirect('posts/index');
    }
    $this->set(compact('post'));
  }
}
?>

The biggest issues here are setting session flash messages and handling the redirects. To handle custom redirects from exceptions, we’ll likely want to create a custom exception class where we can attach routing data:

<?php
//  app /Lib/Exception/AppException.php
class AppException extends CakeException {
  public function setRoute($route = null) {
    $this->_attributes['route'] = $route;
  }
  public function getRoute() {
    return $this->_attributes['route'];
  }
  public function hasRoute() {
    return isset($this->_attributes['route']);
  }
}
?>

Whenever we wish to use this exception, we simply do the following:

<?php
App::uses('AppException', 'Lib/Exception');
$exception = new AppException("Some error occurred");
$exception->setRoute('account/index');
throw $exception;
?>

Note: This isn’t very fluent api. A possible solution would be to do something like:

<?php
//  app /Lib/Exception/AppException.php
class AppException extends CakeException {
  public function __construct($message, $code = 0) {
    parent::__construct($message, $code);
    return $this;
  }
  public function setRoute($route = null) {
    $this->_attributes['route'] = $route;
    return $this;
  }
  public function getRoute() {
    return $this->_attributes['route'];
  }
  public function hasRoute() {
    return isset($this->_attributes['route']);
  }
}
?>

and then simply do throw new AppException("Some error message")->setRoute('account/index');.

Next, you’ll want to create a custom ErrorHandler. CakePHP allows you to override the built-in one, and while we want most of the original internals, we’ll override for our very own exception:

<?php
App::uses('AppException', 'Lib/Exception');
App::uses('ErrorHandler', 'Error');
class AppErrorHandler extends ErrorHandler {
 public static function handleException(Exception $exception) {
   if ($exception instanceof AppException) {
     $element = 'default';
     $message = $exception->getMessage();
     $params = array('class' => 'error');
     CakeSession::write('Message.flash', compact('message', 'element', 'params'));
     if ($exception->hasRoute()) {
       $controller = self::_getController($exception);
       return $controller->redirect($exception->getRoute());
     }
   }
   return parent::handleException($exception);
 }
/**
 * Get the controller instance to handle the exception.
 * Override this method in subclasses to customize the controller used.
 * This method returns the built in `CakeErrorController` normally, or if an error is repeated
 * a bare controller will be used.
 *
 * @param Exception $exception The exception to get a controller for.
 * @return Controller
 */
  protected static function _getController($exception) {
    App::uses('CakeErrorController', 'Controller');
    if (!$request = Router::getRequest(true)) {
      $request = new CakeRequest();
    }
    $response = new CakeResponse(array('charset' => Configure::read('App.encoding')));
    try {
      if (class_exists('AppController')) {
        $controller = new CakeErrorController($request, $response);
      }
    } catch (Exception $e) {
    }
    if (empty($controller)) {
      $controller = new Controller($request, $response);
      $controller->viewPath = 'Errors';
    }
    return $controller;
  }
}
?>

Now that this is available, lets configure it in our core.php:

<?php
App::uses('AppErrorHandler', 'Lib/Exception');
App::uses('AppException', 'Lib/Exception');
Configure::write('Exception', array(
 'handler' => 'AppErrorHandler::handleException',
  'renderer' => 'ExceptionRenderer',
  'log' => true
));
?>

And presto! Now you can throw exceptions with custom routes associated! Now you can start doing the following:

<?php
class NoPostPermissions extends AppException {
  public function __construct($message, $code = 0) {
    $message = "You can't view this";
    parent::__construct($message, $code);
    $this->setRoute('users/home');
    return $this;
  }
}
?>

And our above example controller with weird exception handling now becomes:

<?php
class PostsController extends AppController {
  public function view($id) {
    $post = $this->Post->findById($id);
    $this->set(compact('post'));
  }
}
?>

Simplify your exception handling logic in controllers today!