New Wishlist site

I’ve been posting a lot about the Zend Framework recently. That’s because I’ve been using it in building a new version of my wish list site. The site is called One Wishlist and is at onewishlist.net. It uses version 0.7 of the Zend Framework. I’ll try moving it to version 0.8 soon.

I’ve made a few changes to the site since the previous version, including substantial design changes, but mostly during the move to the Zend Framework I have added hooks for new features that are planned but not yet implemented. Many of these are the result of feedback from the previous site, so thanks to everyone who made suggestions and comments, the features are on their way.

Meanwhile, expect more Flash posts in the next couple of weeks as I have a new flash game to develop for a client.

Managing 404 errors in the Zend Framework

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.

Zend_View helpers in include path

N.B. This article applies to version 0.7 of the Zend Framework. The first solution suggested below is now incorporated in version 0.8 of the framework.

The Problem

I’m using the Zend Framework at the moment and came across a problem with creating custom helpers for the Zend_View class. All is fine if you place your helpers either within the zend framework’s own helpers folder (not a good idea) or within a directory in your site structure. The problem arises if you place the helpers in a folder in the PHP include path and don’t know the full path to the folder.

Why would that happen? Well, here’s my set-up – on each server I have a folder that is added to the include path. This folder has the same structure on all my servers – development, testing and production – but may be in a different location on some of them. That doesn’t matter because I can add the correct folder to PHP’s include path in the php.ini configuration file on each server.

In the include folder I place a number of common files, including the Zend framework itself and my custom files for use with the framework. To keep things simple I duplicate the structure of the Zend Framework library files within my own custom files, as recommended in the Zend Framework documentation. My own files are placed within a fiolder called Bigroom, just as the Zend files are placed within a folder called Zend.

So, my Zend_View helpers are in the folder at Bigroom/View/Helper within the include path. The helpers are all named accordingly too, e.g. Bigroom_View_Helper_SelectCountry for a select box listing all countries. So far so good and in keeping with the recommendations in the framework documentation.

However, to add my helpers to the set of helpers in the Zend Framework, I have to know the full path to the helpers, not the path relative to the PHP include path, and not just the class name. This is not a bug, it’s how the helpers part of the framework is designed. It’s just a bit odd when compared to the rest of the framework. The code to add the helper directory to the helpers might look like this on my production and test servers -

$view = new Zend_View();
$view->addHelperPath( '/home/web/phpincludes/Bigroom/View/Helper',
                      'Bigroom_View_Helper' );

But on my development system, which is windows based, it would look like this -

$view = new Zend_View();
$view->addHelperPath( 'C:\php\includes\Bigroom\View\Helper',
                      'Bigroom_View_Helper' );

This seems to break with the normal behaviour of the Zend Framework, in which classes are automatically found from within the include path based on their class name. It also makes it difficult to create portable code since one must ensure that the include path is exactly the same on all systems using the code.

I have two solutions to this. The first involves altering the code in the Zend Framework, while the second, my preferred option, does not.

Solution 1

In the first solution, we make two small modifications to the Zend_View_Abstract class.

First, in the _loadClass method replace the line

} elseif (is_readable($dir . $file)) {

with

} elseif (Zend::_isReadable($dir . $file)) {

If you do all your work on the same platform, this will suffice. But for cross platform use we also need to check that the directory separators are the same. So the second change is in method _addPath where you need to add the statement

$dir = str_replace( array('/', '\\'), DIRECTORY_SEPARATOR, $dir );

after

$dir = rtrim($dir, '\\/' . DIRECTORY_SEPARATOR) 
                . DIRECTORY_SEPARATOR;

Now you can add helper paths using a path relative to the include path and with either / or \ as the directory separator. So portable code to add my helpers becomes

$view = new Zend_View();
$view->addHelperPath( 'Bigroom/View/Helper',
                      'Bigroom_View_Helper' );

There are, however, two drawbacks to this approach. First it involves modifying the Zend framework itself. That’s not a good idea since you have to remember to redo the modifications when upgrading to a new version of the framework. And secondly, Zend::isReadable is significantly slower than is_readable, and here it’s being used every time you use a helper. The speed hit probably won’t be significant but it would be nice to avoid it.

Solution 2

My second solution to the problem is to extent the Zend_View class with my own Bigroom_View class that implements a new function for adding helper paths based on the class prefix only and looks for the files within the include path. This is in keeping with the normal operation of the Zend Framework where a class name is all that’s needed to find that class within the include paths.

Here’s the code

require_once 'Zend/View.php';

class Bigroom_View extends Zend_View
{
    public function addHelperPrefix( $classPrefix )
    {
        $path = DIRECTORY_SEPARATOR
              . str_replace('_', DIRECTORY_SEPARATOR, $classPrefix);
        $dirs = explode( PATH_SEPARATOR, get_include_path() );
        foreach( $dirs as $dir )
        {
            $fullpath = $dir . $path;
            if( is_readable( $fullpath ) )
            {
                $this->addHelperPath( $fullpath, $classPrefix );
            }
        }
    }
}

Now I use Bigroom_View instead of Zend_View and add helpers as follows.

$view = new Bigroom_View();
$view->addHelperPrefix( 'Bigroom_View_Helper' );

Which seems a lot neater to me.

Why I like the Zend Framework

I’ve been researching the Zend Framework for PHP in relation to a project I’m working on and I have to say I’m now a fan of it. There’s lots to like about it, and some to dislike about it too. But to me the best aspect of it is the flexibility it offers. Unlike some frameworks, you don’t have to use the complete package. Instead you can pick and choose which bits to use and plug it together with your own classes to get a complete product that meets your particular needs.

For myself, I’m using the controller and view aspects of the framework with a few minor additions of my own. Alongside this I’m using the model set-up that I’ve evolved over a number of projects, with the addition of a few components from the Zend_Framework. Slotting the framework into my current workflow has proven remarkably easy.

If I was to make one complaint it would be about the documentation. It has some gaps in it at the moment but it’s early days yet. Much of the detail I’ve discovered has been by inspecting the source code, but the code itself is clearly structured and well commented so it’s relatively easy to rummage through and find what you’re looking for.

If you’re looking for a PHP framework it’s worth checking out more than one since they all have different advantages and disadvantages. Here’s a short list to consider.