Game Designer, Programmer, Choreographer

Managing 404 errors in the Zend Framework

Posted on

This applies to version 0.7 of the Zend Framework. Changes for later versions of the framework are at the end of the article.

Early versions of the Zend Framework had a noRoute action that was called when the correct action couldn't be found. This was a way to deal with some page not found errors. At some point it was dropped - I don't know when or why because I only started using the Zend Framework recently. It's still possible to handle non-existent actions using the __call() method of the controller class. But there's no obvious way to deal with all page not found errors in one place, including instances where the controller doesn't exist.

The Zend Framework is based around controllers and actions, using URL's of the form http://www.example.com/controller/action. If no action is specified the index action is used, and if no controller is specified the index controller is used. You can modify the mapping of URLs to controllers and actions by setting up different rewrite routers in the front controller.

A 404 error should occur when the controller or action specified in the URL aren't defined. But instead of a 404 error, the Zend Framework throws an exception because the controller class or the action method can't be found. There's nothing worng with this - it leaves it for each developer to decide what to do. So what should we do?

To generate an appropriate 404 error with a custom page we need either to intercept the request before the error occurs or to catch the exception after the error occurs, and in either case to redirect to an appropriate controller and action.

I created a controller plug-in to do just that. In the pre-despatch method it checks the controller class can be loaded and that it contains the required action. If either is not the case it redirects the request to the index action of the noroute controller. Then one just creates an appropriate noroute controller to display the page not found error.

Here's the plug-in code.

require_once 'Zend/Controller/Plugin/Abstract.php';

class Bigroom_Controller_Plugin_Noroute
                           extends Zend_Controller_Plugin_Abstract
{
    public function preDispatch(
                       Zend_Controller_Request_Abstract $request )
    {
        $dispatcher = Zend_Controller_Front::getInstance()
                                                ->getDispatcher();
        
        $controllerName = $request->getControllerName();
        if (empty($controllerName)) {
            $controllerName = $dispatcher->getDefaultController();
        }
        $className = $dispatcher
                          ->formatControllerName($controllerName);
        if ($className)
        {
            try
            {
                // if this fails, an exception will be thrown and
                // caught below, indicating that the class can't
                // be loaded.
                Zend::loadClass($className,
                           $dispatcher->getControllerDirectory());
                $actionName = $request->getActionName();
                if (empty($actionName)) {
                    $actionName = $dispatcher->getDefaultAction();
                }
                $methodName = $dispatcher
                                  ->formatActionName($actionName);
                
                $class = new ReflectionClass( $className );
                if( $class->hasMethod( $methodName ) )
                {
                    // all is well - exit now
                    return;
                }
            }
            catch (Zend_Exception $e)
            {
                // Couldn't load the class. No need to act yet,
                // just catch the exception and fall out of the
                // if
            }
        }
    
        // we only arrive here if can't find controller or action
        $request->setControllerName( 'noroute' );
        $request->setActionName( 'index' );
        $request->setDispatched( false );
    }
}

Simply register this plug-in with the front controller and set-up the NorouteController class to display the error page. And remember to send a 404 error header from the noroute controller.

The plug-in doesn't deal with modules - mainly because I don't use modules and couldn't decide whether the NorouteController class should be global or per-module. It shouldn't be too hard to add module handling to the code.

Zend Framework 0.9

For version 0.9 of the framework you need to change Zend::loadClass to Zend_Loader::loadClass. With this change the plug-in works again as intended.

Share this post or a comment online -


Also in the collection PHP