Scaling Zend_Form

An adage often exchanged between Zend Framework enthusiasts goes something like this: “The bad thing about Zend Framework is that there’s a dozen ways to do anything. The great thing about Zend Framework is that there’s a dozen ways to do anything.” To a degree, this is a boon for the project. I think it’s fair to say that it’s one of the more flexible framework projects out there when it comes to how to do things with it.

I came across an instance using Zend_Form recently where the level of flexibility offered was a bit of a double-edged sword. In order to provide maximum flexibility per form element instance, each has not only their own filter, validator, and decorator instances, but also a plugin loader instance for each of these three types of plugins. These add up quickly when you have a form with several hundred elements in it.

But maybe you don’t have a reason for needing each element to have its own plugin loaders. I honestly can’t see a use case for that, but I’ve heard it claimed that one exists. For large forms, you can improve performance and memory usage by manually instantiating a plugin loader for each type of plugin, configuring them, and then having all elements added to the form use those plugin loader instances. To do this, subclass Zend_Form like so:

require_once 'Zend/Form.php';
require_once 'Zend/Loader/PluginLoader.php';

class Custom_Form extends Zend_Form
{
    private $_elementLoaders;

    public function init()
    {
        // Clear default form decorator paths so elements don't inherit them
        $this->getPluginLoader('decorator')->clearPaths();

        // Instantiate and configure central plugin loaders for elements
        $this->_elementLoaders = array();

        $this->_elementLoaders['decorator'] = new Zend_Loader_PluginLoader();
        // $this->_elementLoaders['decorator']->addPrefixPath( ... );

        $this->_elementLoaders['validate'] = new Zend_Loader_PluginLoader();
        // $this->_elementLoaders['validate']->addPrefixPath( ... );

        $this->_elementLoaders['filter'] = new Zend_Loader_PluginLoader();
        // $this->_elementLoaders['filter']->addPrefixPath( ... );
    }

    public function addElement($element, $name = null, $options = null)
    {
        if (!is_array($options)) {
            $options = array();
        }

        // A plugin loader is implicitly created if default decorators are loaded
        $options['disableLoadDefaultDecorators'] = true;

        // Add the element to the form
        parent::addElement($element, $name, $options);

        // Configure the element to use the central plugin loaders
        $element = $this->getElement($name);
        foreach ($this->_elementLoaders as $type => $loader) {
            $element->setPluginLoader($loader, $type);
        }

        // Now load default decorators for the element
        $element->loadDefaultDecorators();

        return $this;
    }
}

I find the Internationalization of Zend_Form page in the Reference Guide to be a bit misleading. While no i18n is done by default, that doesn’t mean that Zend_Translate components are not still loaded by default. In my opinion, Zend_Form should have been designed such that you would enable this feature if you needed it rather than disabling it if you didn’t. Be that as it may, here’s how you can handle turning it off if you don’t need it to gain a little extra performance.

class Custom_Form extends Zend_Form
{
    public function init()
    {
        $this->setDisableTranslator(true);
    }

    public function addElement($element, $name = null, $options = null)
    {
        if (!is_array($options)) {
            $options = array();
        }
        $options['disableTranslator'] = true;
        return parent::addElement($element, $name, $options);
    }
}

If you are currently running ZF 1.6.2, changeset 12201 was committed recently to address a performance issue with translation (which applies whether it’s disabled or not) of multi elements. It should be included in 1.7, but in the meantime the patch is easy to apply and shouldn’t conflict with any existing code using it.

The only other bottleneck that I noticed was how quickly calls to render individual form elements added up. I’m not sure if there’s any way around this or if any particular area of the task is causing a bottleneck, but it’s something that I hope to investigate further in the future. For the time being, I hope the changes I’ve mentioned here are helpful to someone. If you have any relevant comments, please feel free to post them to this entry. Thanks in advance for your contributions to the discussion!

4 Comments

  1. An interesting approach, but personally I’d rather make a few small forms than a gargantuan 100-field one. For example, I’d hate to see the browser/os/something crash while filling that…

  2. Whether or not several smaller forms would work really depends on the requirements of the application. Dividing a form into subforms only works if you only display a subform worth of fields per page load and it’s the obvious solution if that’s conducive to fulfilling the requirements. Otherwise, dividing the form into subforms does nothing but add unnecessary overhead.

    The intent of this blog entry wasn’t to suggest that either approach of having one larger form or several smaller forms is better than the other, but to describe how to modify Zend_Form such that it performs better in situations where the developer is forced by requirements to have one large form and some of the flexibility provided by Zend_Form is not necessarily needed.

    I personally used the approach I described with a form having over 250 fields and was able to reduce the page load time from 4.5 to 1.5 seconds. This still isn’t an ideal request per second rating, obviously, but it’s a significant improvement and I think it points to an area where Zend_Form could be improved as well.

  3. till says:

    Interesting bits! :-) Did you open issues for your findings? E.g. disabling the translator?

  4. @till The referenced SVN changeset was in response to my finding regarding the translation bottleneck, but everything else I’ve described here is modifying Zend_Form behavior that exists by design. So, there aren’t really any issues to report as this is the way that these components are intended to function and my use cases are for them are (apparently) unorthodox.