Writing a custom error handler

Published on and tagged with cakephp  tip

Let’s assume we do not want to show any errors when a non-existing controller is called. The user should simply be redirected to the homepage in such a situation. To accomplish that we have to write a custom error handler.

First, we have to create a file called “error.php” in the “app” directory. We add an “AppError” class to this file which extends the default error handler (cake/libs/error.php) and override the “missingController” function.

// app/error.php
class AppError extends ErrorHandler {
    public function missingController($params) {
        $this->controller->redirect('/');
    }
}

That’s it? Unfortunately, no. This solution works when we use a debug level > 0, but with a debug level of 0 we get a 404 error. We find the reason for that behaviour in the following lines of the default error handler’s constructor:

// CakePHP 1.1
if (DEBUG > 0 || $method == 'error') {
    call_user_func_array(array(&$this, $method), $messages);
} else {
    call_user_func_array(array(&$this, 'error404'), $messages);
}

// CakePHP 1.2
if ($method !== 'error') {
    if (Configure::read() == 0) {
        $method = 'error404';
        if (isset($code) && $code == 500) {
            $method = 'error500';
        }
    }
}

So we have to write our own constructor in our class (fortunately, we can copy almost the entire constructor of the default error handler). Now our AppError class looks like:

// CakePHP 1.1
class AppError extends ErrorHandler {
    public function __construct($method, $messages) {
        static $__previousError = null;
        $this->__dispatch =& new Dispatcher();

        if ($__previousError != array($method, $messages)) {
            $__previousError = array($method, $messages);

            if (!class_exists('AppController')) {
                loadController(null);
            }
	
            $this->controller =& new AppController();
            $this->__dispatch->start($this->controller);
	
            if (method_exists($this->controller, 'apperror')) {
                return $this->controller->appError($method, $messages);
            }
        } else {
            $this->controller =& new Controller();
        }
			
        call_user_func_array(array(&$this, $method), $messages);
    }
		
    public function missingController($params) {
        $this->controller->redirect('/');
    }
}

// CakePHP 1.2
class AppError extends ErrorHandler {
    public function __construct($method, $messages) {
        App::import('Core', 'Sanitize');
        static $__previousError = null;

        if ($__previousError != array($method, $messages)) {
            $__previousError = array($method, $messages);
            $this->controller =& new CakeErrorController();
        } else {
            $this->controller =& new Controller();
            $this->controller->viewPath = 'errors';
        }

        $options = array('escape' => false);
        $messages = Sanitize::clean($messages, $options);

        if (!isset($messages[0])) {
            $messages = array($messages);
        }

        if (method_exists($this->controller, 'apperror')) {
            return $this->controller->appError($method, $messages);
        }

        if (!in_array(strtolower($method), array_map('strtolower', get_class_methods($this)))) {
            $method = 'error';
        }

        $this->dispatchMethod($method, $messages);
        $this->_stop();
    }

    public function missingController($params) {
        $this->controller->redirect('/posts');
    }
}

Thanks to PhpNut for the hint with the constructor, and to drSwank for asking in the irc channel.

Update 2009-01-31: Adapted this article for CakePHP 1.2.

33 comments baked

  • Felix Geisendörfer

    Thanks for this post. Another option to do this is the following:

    class AppController extends Controller
    {
    function appError($method, $params)
    {
    $this->controller->redirect(‘/’);
    }
    }

  • Konrad

    Nice, might came in handy. Just one thing about the layout: With IE 6.0 the content get’s cut off right at the border, I suppose it’s ment to display some sort of scrollbar?

  • cakebaker

    @Felix: Thanks for the addition. Something learned :)

    @Konrad: Hm, I am sorry, but here on wordpress I do not have access to the layout.

  • Konrad

    Actually, scratch that, I’m not getting a scrollbar in FF (1.5.x) either.

    http://img81.imageshack.us/img81/8122/codesnippet1rv.jpg

    So in what Browser exactly can you seen the complete Code? :)

  • cakebaker

    @Konrad: Well, I use FF 1.5.x without any problems. Anyway, I posted the code as a snippet on cakeforge: http://cakeforge.org/snippet/detail.php?type=snippet&id=79

  • rossoft

    @Konrad: you can always select the code and paste in your editor

  • Richard@Home » Blog Archive » links for 2006-06-20

    […] cake baker » Writing a custom error handler A very useful error handler for missing controllers. (tags: cakephp controller errorhandler) […]

  • morris

    I’m curious if, after this new Cake update (that takes care of a security vulnerability through the error page), this custom error handler is now flawed as well?

    I had been using this (above) for some time, and it worked great. But after recent cake update & patch (as of Aug 17th) my error page breaks… giving Fatal Error .. in app_controller. Doesn’t seen to be able to call PartyAuth->set() (a custom method we had used).

    Thanks for your time…!

  • cakebaker

    @morris: Hm, it works fine for me.

  • Seph

    @Felix:
    I think you meant
    $this->redirect(’/’);
    and not
    $this->controller->redirect(’/’);

    Good tip, though. Thanks.

  • Tim Daldini

    I read this post months ago and really struggled with how the errorhandling process. I had very little experience with CakePHP or even PHP back then so I simply did not understand it.

    But reading it again, with all the experience I have gained by now, I do, and it seems obvious that Felix’ approach is the way to go when handling errors inside the application.

    The Copypaste approach on the other hand, will cause code to break when the errorhandling implementation in CakePHP -eventually- changes. And it seems it allready has looking at the latest public beta. So yeah, morris is right.

    However, I never would have known without this post, so thanks a lot.

  • cakebaker

    @Tim: Yes, I agree with you that the copy/paste approach is risky, but sometimes it is the only chance to accomplish something. I think in the meantime it is no longer necessary to copy/paste the code, you can now probably accomplish the desired functionality by changing the debug value in the constructor with Configure::write(‘debug’, 1).

    Anyway, I am glad this post resp. the comments helped you :)

  • chris

    Has anyone overcome the following – I added Felix’s code but by the time it redirects, the header has already been sent so I get the error (with Felix’s proc at the top):

    class AppController extends Controller { function appError($method, $params) { $this->controller->redirect(‘/’); } }
    Warning: session_start() [function.session-start]: Cannot send session cache limiter – headers already sent (output started at /home/technica/cake/app/error.php:8) in /home/technica/cake/cake/libs/session.php on line 154

    Thanks very much
    Chris

  • chris

    with bowed head i admit i’d forgotten the <?php..

    however still getting the error:

    Fatal error: Cannot redeclare class AppController in /home/technica/cake/app/error.php on line 8

  • cakebaker

    @chris: Well, if you use the code posted by Felix you don’t need the error.php file. The function is added to the AppController in app/app_controller.php.

    HTH

  • chris

    Thanks hugely – that fixed it! (Mine was in the cake directory, and I was assuming Felix was trying to keeping customization within the app/ directory.)

  • cakebaker

    @chris: Copy /cake/app_controller.php to /app/app_controller.php and make the changes there as. You shouldn’t touch the cake directory ;-)

  • List of

    I have been trying to define an “error404.thtml” file for my application and is hasn’t been working.
    I think I’ll give this approach a shot and see how it goes.
    Thanks

  • cakebaker

    Good luck!

  • dezpo

    and is it possible to handle PHP errors and notices?

  • cakebaker

    @dezpo: No, the ErrorHandler resp. AppError class is only thought for handling cake-specific error situations like missing view, missing controller, etc. For handling PHP errors have a look at the error handling functions of PHP.

    Hope that helps!

  • handsofaten

    Quick question related to this… when I set debug to 0 (in CakePHP 1.2 RC2), all malformed urls show up as blank pages with the title “Errors” — how can I just get a 404 message to appear? I tried putting “error404.thtml” into the app/views/errors folder, but that didn’t seem to do anything.

    thanks.

  • cakebaker

    @handsofaten: Hm, it is strange that you get blank pages. Theoretically, you should get a “Not found” error message. Did you remove $content_for_layout from the default layout?

  • handsofaten

    Thanks for the reply, baker. No, $content_for_layout is there. It prints $title_for_layout as “Errors”, closes the title tag, and then stops. All I really want is a simple 404 message.

  • cakebaker

    @handsofaten: What follows after the closed title tag in your layout?

  • handsofaten

    typical header stuff:

    echo $html->charset();
    echo $html->meta(‘icon’);

    echo $html->css(‘bluestar’);
    echo $html->css(‘lightbox’);

    echo $scripts_for_layout;

  • cakebaker

    @handsofaten: What happens if you comment out those calls to the Html helper? My guess is that the Html helper doesn’t get loaded and that this causes the blank page.

    Btw: Which version of cake do you use?

  • Carlos

    Does anyone has problems with the code? i’m getting a error on:
    $this->__dispatch->start($this->controller);

  • cakebaker

    @Carlos: Thanks for the hint! I modified the article so it now works with CakePHP 1.2, too. With those changes, the error should disappear. Good luck!

  • pokoot

    DOes anyone here know how to access the session variables on error handler?

    I have tried a lot of things like app import session and still no luck. Tried even $_SESSION[‘var_name’] and still no luck.

    Your solution is much appreciated.

    Thank you!

  • cakebaker

    @pokoot: Hm, $this->controller->Session->read(‘key’); doesn’t work?

  • anonimo

    First, create a new layout and call it error.ctp (place in the app/views/layouts, of course).

    Then add this to your Class AppController:

    // app/app_controller.php
    
    function beforeRender () {
       $this->_setErrorLayout();
    }
    
    function _setErrorLayout() {
       if($this->name == 'CakeError') {
           $this->layout = 'error';
       }
    }
  • cakebaker

    @anonimo: Thanks for your snippet for setting a special error layout!

Bake a comment




(for code please use <code>...</code> [no escaping necessary])

© daniel hofstetter. Licensed under a Creative Commons License