Decorators with Zend_Form

May 5, 2008

Tutorials, Zend Framework

Zend_Form
has been lauded by many as a welcome addition to Zend Framework, and a
flexible solution to the problem of forms. That said, one point of
flexibility it offers has proved to be a pain point for many developers:
decorators. This tutorial aims to shed some light on decorators, as well as
provide strategies for creating your own decorators and combining them in
creative ways to customize the output your forms generate.

Background

When designing Zend_Form, a primary goal was that it should
generate the markup necessary to display each element and the form itself.
The rationale was that injecting values and metadata into the element markup
and reporting errors is often a tedious, repetitive task. Any form solution
needed to address this and make these actions trivial.

Of course, the problem with generating form markup is that there is no
standard way to do it. The approaches vary greatly from developer to
developer and project to project. So, any solution for this had to be
flexible enough to accomodate a variety of approaches.

Another objective was that Zend_Form not be tied to Zend_View.
While it’s true that the default decorators actually do utilize
Zend_View, the solution needed to be flexible enough that
should a developer choose not to utilize Zend_View, they could.

With all these goals in mind, the solution finally presented itself: use the
principles of the href="http://en.wikipedia.org/wiki/Decorator_pattern">Decorator
Pattern. A decorator traditionally is used as a way to extend the
functionality of a class by wrapping it. This allows the developer the
ability to add or modify existing behavior while making calls to the same
API. Decorators may wrap other decorators, allowing for a layered approach
to extension. The decorator itselfis used in place of the original object,
as it follows the same API; it simply provides new or additional
functionality to the original.

As an example, consider a Window class. You might then create a
WindowDecorator that displays the window, and additional decorators for
showing the scrollbars, window title, etc; each decorator is responsible for
a single aspect of the final display. Instantiation might look like this:

$window = new WindowScrollbarDecorator(
    new WindowTitleDecorator(
        new WindowDecorator(
            new Window()
        )
    )
);

To render the final window:

$window->render();

This would execute the WindowScrollbarDecorator::render()
method, which would in turn call the
WindowTitleDecorator::render() method, which would then call
WindowDecorator::render(), which would finally either call on
the Window::render() method or simply use object state from it.
The final result is a window with a title and scrollbars.

Decorators present an ideal solution for Zend_Form, in that a
developer may want to selectively determine what to display in their final
output. The final solution settled on is a modified decorator; instead of
decorating the object, we decorate the content generated, but we do so using
metadata from the element or form object. Whereas a traditional decorator
operates from the outside in, Zend_Form‘s operates from the
inside out, layering content outward.

Basics of Operation

When decorating, you have the ability to either prepend, append, or replace
(which could include wrapping) the content passed in. The initial content is
an empty string, and then each decorator works on the content from the
previously executed decorator.

Let’s look at how the default decorators for most elements work. The default
decorators are ViewHelper, Errors,
HtmlTag, and finally Label. By default, the
ViewHelper decorator replaces any content provided; the Errors decorator
appends the content provided; the HtmlTag decorator wraps content provided
and the Label decorator prepends content provided. (In all cases except the
ViewHelper, the placement — to append, prepend, or wrap — is
configurable.) To visualize execution, the final execution call looks
something like this:

$label->render($htmlTag->render($errors->render($viewHelper->render(''))))

Step by step, let’s look at how the content is built:

  •         <input name="foo" id="foo" type="text" value="" />
        

  •         <input name="foo" id="foo" type="text" value="" />
            <div class="error"><ul>
                <li>...</li>
            </ul></div>
        

  •         <dd>
            <input name="foo" id="foo" type="text" value="" />
            <div class="error"><ul>
                <li>...</li>
            </ul></div>
            </dd>
        

  •         <dt><label for="foo" class="optional">Foo</label><dt>
            <dd>
            <input name="foo" id="foo" type="text" value="" />
            <div class="error"><ul>
                <li>...</li>
            </ul></div>
            </dd>
        

As you can see, each decorator does one thing, and the final result is an
aggregation of the content produced by each decorator.

Customizing Output Using Standard Decorators

To get an idea of how to customize output, first you need to know the
baseline: what the standard, registered decorators are for each object type.
The standard decorators, in the order they are registered, for most elements
are:

  • ViewHelper
  • Errors
  • HtmlTag (<dd>)
  • Label (with wrapping <dt> tag)

For the form object, the standard decorators are:

  • FormElements (iterates through all elements, display groups, and sub
    forms, rendering each)
  • HtmlTag (<dl>)
  • Form

Display groups and sub forms share the following decorators by default:

  • FormElements
  • Fieldset
  • DtDdWrapper (wraps fieldset in a <dd>, and prepends with an empty
    <dt>)

One easy way to customize output is to either add or modify options on a
decorator. For instance, if you decide you want your label to
follow your element, instead of precede it, you could change the
placement option:

$label = $element->getDecorator('label');
$label->setOption('placement', 'append');

There are a variety of options available for most decorators; you will need
to read the manual and/or API documentation to get the full details.

Another easy way to customize output is to remove a decorator. If
you don’t want to display a label, for instance, remove that decorator:

$element->removeDecorator('label');

Unfortunately, there are not currently methods for inserting decorators at
specific locations in the decorator stack, so if you find you need to insert
a new decorator in the middle of the stack, the best way is to simply reset
the stack. For example, if you wanted to add a Description following the
element (perhaps a paragraph detailing the purpose of the element), you
could do the following:

$element->setDescription($desc);
$element->setDecorators(array(
    'ViewHelper',
    'Description',
    'Errors',
    array('HtmlTag', array('tag' => 'dd')),
    array('Label', array('tag' => 'dt')),
));

While addDecorator() and addDecorators() methods
exist, typically you will be using setDecorators() unless you
start out with very minimal decorators to begin with.

When adding a decorator, you have the option to alias it. What this
does is allow you to store the decorator using a different name — which
allows you to retrieve it from the stack by that name. This is primarily
useful when you need to add two or more of the same type of decorator; in
fact, in such a situation, if you do not alias, the last registered
decorator of that type will overwrite all other instances! You accomplish
aliasing by passing an array as the decorator type, with a single key/value
pair with the alias as the key, and the decorator type as the value. For
instance, if you needed to use two different HTML tags in your stack, you
could do something like the following:

$element->setDecorators(array(
    'ViewHelper',
    'Description',
    'Errors',
    array(array('elementDiv' => 'HtmlTag'), array('tag' => 'div')),
    array(array('td' => 'HtmlTag'), array('tag' => 'td')),
    array('Label', array('tag' => 'td')),
));

In the above example, the element content is wrapped in an HTML div, which
is then in turn wrapped in a table data cel. The two are aliased as
‘elementDiv’ and ‘td’, respectively.

Standard Decorators

Now that we know how to manipulate the decorator stack and the individual
decorators, what standard decorators are available?

  • Callback: execute a specified PHP callback to return content
  • Description: render the item’s description property
  • DtDdWrapper: wrap content in a <dd> and prepend with an
    empty <dt>
  • Errors: render an unordered list of the item’s errors, if
    any
  • Fieldset: render the content provided in a fieldset, using the
    item’s legend property for a legend if available
  • FormElements: iterate through a form, sub form, or display group,
    rendering each item (which could be an element, display group, or sub
    form)
  • Form: wrap content in a <form> tag, using the form object’s
    metadata as attributes
  • HtmlTag: wrap content in an HTML tag, or prepend or append the
    content with a given tag (and optionally use either just the open or
    close tag)
  • Image: render a form image based on the current element
  • Label: render an element’s label (prepends by default)
  • ViewHelper: render an element by utilizing a view helper. the
    view helper used is pulled from the element’s ‘helper’ property, if
    available, but can also be specified explicitly by passing a ‘helper’
    option to the decorator. Replaces content by default.
  • ViewScript: render an element by rendering a specified view
    script. Replaces content by default.

Each of the above appends the content provided by default, unless otherwise
specified.

Example: Table Layout

One common request is to be able to render a form as an HTML table. How can
this be accomplished?

For purposes of this example, we will assume that there should be one row
per element. Standard elements will use two columns per row, one for the
label and one for the element and any reported errors; buttons will be
displayed in the second column with no label.

For standard elements, you would set decorators like the following:

$element->setDecorators(array(
    'ViewHelper',
    'Errors',
    array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
    array('Label', array('tag' => 'td'),
    array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));

For button and image elements, we’d use the following:

$element->setDecorators(array(
    'ViewHelper',
    array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
    array(array('label' => 'HtmlTag'), array('tag' => 'td', 'placement' => 'prepend')),
    array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));

The form itself would look like this:

$form->setDecorators(array(
    'FormElements',
    array('HtmlTag', array('tag' => 'table')),
    'Form',
));

The following output might then be generated:

<form enctype="application/x-www-form-urlencoded" action="" method="post">
<table>
    <tr>
        <td><label for="username" class="optional">Username:</label></td>
        <td class="element">
            <input type="text" name="username" id="username" value="">
        </td>
    </tr>
    <tr>
        <td><label for="firstname" class="optional">First Name:</label></td>
        <td class="element">
            <input type="text" name="firstname" id="firstname" value="">
        </td>
    </tr>
    <tr>
        <td><label for="lastname" class="optional">Last Name:</label></td>
        <td class="element">
            <input type="text" name="lastname" id="lastname" value="">
        </td>
    </tr>
    <tr>
        <td></td>
        <td class="element">
            <input type="submit" name="save" id="save" value="Save">
        </td>
    </tr>
</table>
</form>

Note: this does not accomodate for display groups and sub forms;
hopefully, it gives enough of an idea that you can figure out how to do so.

“That’s nice,” you say, “but I don’t really want to have to set those
decorators for each and every form element.” Well, you don’t have to, as
there are a variety of methods to accomodate this.

First, there’s the setElementDecorators() method of the form
object. This method will set all decorators for all currently
registered form elements (not elements registered after calling
it):

$form->setElementDecorators(array(
    'ViewHelper',
    'Errors',
    array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
    array('Label', array('tag' => 'td'),
    array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
));

You could do this once, and then loop through the form, looking for submit,
reset, button, and image elements to set their decorators.

Another easy option is subclassing the form object, and defining these as
arrays which are passed to the elements:

class My_Form_Registration extends Zend_Form
{
    public $elementDecorators = array(
        'ViewHelper',
        'Errors',
        array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
        array('Label', array('tag' => 'td'),
        array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
    );

    public $buttonDecorators = array(
        'ViewHelper',
        array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
        array(array('label' => 'HtmlTag'), array('tag' => 'td', 'placement' => 'prepend')),
        array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
    );

    public function init()
    {
        $this->addElement('text', 'username', array(
            'decorators' => $this->elementDecorators,
            'label       => 'Username:',
        );
        $this->addElement('text', 'firstname', array(
            'decorators' => $this->elementDecorators,
            'label       => 'First Name:',
        );
        $this->addElement('text', 'lastname', array(
            'decorators' => $this->elementDecorators,
            'label       => 'Last Name:',
        );
        $this->addElement('submit', 'save', array(
            'decorators' => $this->buttonDecorators,
            'label       => 'Save',
        );
    }

    public function loadDefaultDecorators()
    {
        $this->setDecorators(array(
            'FormElements',
            array('HtmlTag', array('tag' => 'table')),
            'Form',
        ));
    }
}

The above method is especially nice as it prevents the default decorators
loading by providing decorators at instantiation time. It also has the
advantage that you can modify your decorators in a single place. Should you
choose to make the properties elementDecorators or
buttonDecorators static, you could even inject the default
decorators you want for the form prior to instantiating it, allowing for
re-use with different decorator stacks.

Finally, you could also create your own element subclasses that override the
loadDefaultDecorators() methods — allowing you to have
different elements based on the type of output you plan to generate.

Example: Full Customization Using the ViewScript Decorator

What if you want to have arbitrary content mixed in with your form elements,
or you have a complex markup you want to use? Decorators may be fine for the
individual form elements, but for the form as a whole, they make little
sense. Enter the ViewScript decorator.

The ViewScript decorator will render a given view script as a partial,
passing the form as the $form view variable. This allows you to
pull out the metadata and/or elements you need and render them directly.
When using one with a form object, it will also typically obviate the need
for Display Groups, as you can do those manually.

As an example, consider the following view script:

<h4>Please register with us!</h4>
<form action="<?= $this->escape($this->form->getAction() ?>"
      method="<?= $this->escape($this->form->getMethod() ?>">

<fieldset>
    <legend>Demographics</legend>
    <p>
        Please provide us the following information so we can know more about
        you.
    </p>

    <?= $this->form->age ?>
    <?= $this->form->nationality ?>
    <?= $this->form->income ?>
</fieldset>

<fieldset>
    <legend>User Information</legend>
    <p>
        Now please tell us who you are and how to contact you.
    </p>

    <?= $this->form->firstname ?>
    <?= $this->form->lastname ?>
    <?= $this->form->email ?>
    <?= $this->form->address1 ?>
    <?= $this->form->address2 ?>
    <?= $this->form->city ?>
    <?= $this->form->state ?>
    <?= $this->form->postal ?>
    <?= $this->form->phone ?>
</fieldset>

<?= $this->form->submit ?>
</form>

If the above form was in the view script “demogForm.phtml”, you could then
attach it to your form as follows:

$form->setDecorators(array(
    array('ViewScript', array('script' => 'demogForm.phtml'))
));

As you can see, this method of rendering a form can be more verbose, but it
also allows you to tweak the form almost infinitely while still gaining the
advantages of error reporting, labeling, etc that decorators provide (by
using the decorators associated with the elements).

Creating a Custom Decorator

Make no mistake about it: there will come a time when the standard
decorators simply will not do the job. You may have markup that’s too
complex to generate with a chain of decorators, you may want to cut down
function calls to optimize your application, you may want to combine
multiple discrete HTML elements as a single Zend_Form element, etc. At this
time, you should start investigating custom decorators.

Anatomy of a Decorator

All decorators in Zend_Form implement
Zend_Form_Decorator_Interface, which simply looks like this:

interface Zend_Form_Decorator_Interface
{
    public function __construct($options = null);
    public function setElement($element);
    public function getElement();
    public function setOptions(array $options);
    public function setConfig(Zend_Config $config);
    public function setOption($key, $value);
    public function getOption($key);
    public function getOptions();
    public function removeOption($key);
    public function clearOptions();
    public function render($content);
}

For your convenience, most of these methods are stubbed in
Zend_Form_Decorator_Abstract, meaning that all you need to do
with your decorators is provide a render() method:

class My_Form_Decorator_Foo extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        // ...
        return $content
    }
}

A decorator stores an instance of the current object as the ‘element’.
However, the element is not restricted to Zend_Form_Element
objects, but can also be forms, display groups, or sub forms. As a result,
you can have decorators that target aspects of any of these object types and
their metadata. Retrieve the current ‘element’ using
getElement().

If your decorator should be view aware, you can attempt to pull the view
object from the element:

$view = $this->getElement()->getView();

If the returned value is null, you do not have a view.

Two properties are also always set: the separator and placement. Your
render method should make use of these when returning content.
When extending Zend_Form_Decorator_Abstract, you can retrieve
these using getSeparator() and getPlacement(),
respectively; otherwise, check for them using getOption(), as
they are passed as options typically. As an example:

public function render($content)
{
    // ...
    $separator = $this->getSeparator();
    $placement = $this->getPlacement();

    switch ($placment) {
        case 'APPEND':
            // append original content with new content
            return $content . $separator . $newContent;
        case 'PREPEND':
            // prepend original content with new content
            return $newContent . $separator . $content;
        default:
            // replace otherwise
            return $newContent;
    }
}

Now that you have the tools for creating decorators, let’s tell the form
and elements how to find them.

Telling Your Form or Element About Your Decorators

Creating custom decorators is great, but unless you tell your form or
elements where to find them, they’re worthless. You need to tell your
objects where to look for your decorators.

Forms

You can tell a form where to look for its decorators using
addPrefixPath():

// Basic usage:
$form->addPrefixPath($prefix, $path, 'decorator');

// Example usage:
$form->addPrefixPath('My_Form_Decorator', 'My/Form/Decorator/', 'decorator');

The second argument, $path indicates a path to classes
containing the given prefix. If that path is on your
include_path, then you can use a relative path; if not, use a
fully-qualified path to ensure that the plugin loader can find the classes.

The third argument is the type of plugin path for which the path applies; in
this case, we’re only looking at decorators, so we use the string
‘decorator’; however, you can also use addPrefixPath() to set
plugin paths for other plugin types, such as elements, validators, and
filters.

You can also setup decorator paths for the various items Zend_Form
aggregates — display groups, sub forms, and elements. This can be done by
using the following methods:

  • addElementPrefixPath($path, $prefix, 'decorator')
  • addDisplayGroupPrefixPath($path, $prefix)

In each case, the setting will apply to any elements or display groups
currently attached to the form, as well as those created or attached later.
You may also pass an elementPrefixPath option during
configuration; the value will be used for any elements created with the
form. As examples:

// Programmatically
$form->addElementPrefixPath('My_Form_Decorator', 'My/Form/Decorator', 'decorator');
$form->addDisplayGroupPrefixPath('My_Form_Decorator', 'My/Form/Decorator');

// at instantiation:
$form = new Zend_Form(array(
    'elementPrefixPath' => array(
        array(
            'prefix' => 'My_Form_Decorator', 
            'path'   => 'My/Form/Decorator/', 
            'type'   => 'decorator'
        ),
    ),
));

// Or using an INI config file:
form.elementPrefixPath.my.prefix = "My_Form_Decorator"
form.elementPrefixPath.my.path   = "My/Form/Decorator"
form.elementPrefixPath.my.type   = "decorator"

// Or using an XML config file:
<form>
    <elementPrefixPath>
        <my>
            <prefix>My_Form_Decorator</prefix>
            <path>My/Form/Decorator</path>
            <type>decorator</type>
        </my>
    </elementPrefixPath>
</form>

Elements

To set a plugin path for an individual element, you can call
addPrefixPath($prefix, $path, 'decorator') on your element
itself, or pass values via the prefixPath configuration key.
This would be primarily useful if you know that only a single or subset of
elements is using custom decorator plugins, or if you need some elements to
use standard decorators and others to use overridden decorators.

The use cases are almost exactly like with Zend_Form:

// Basic usage:
$element->addPrefixPath($prefix, $path, 'decorator');

// Example:
$element->addPrefixPath('My_Form_Decorator', 'My/Form/Decorator', 'decorator');

// Configuring at instantiation:
$element = new Zend_Form_Element('foo', array(
    'prefixPath' => array(
        array(
            'type'   => 'decorator', 
            'path'   => 'My/Form/Decorator/', 
            'prefix' => 'My_Form_Decorator'
        ),
    ),
));

// or creating with a form object:
$form->addElement('text', 'foo', array(
    'prefixPath' => array(
        array(
            'type'   => 'decorator', 
            'path'   => 'My/Form/Decorator/', 
            'prefix' => 'My_Form_Decorator'
        ),
    ),
));

// With an INI config:
form.foo.options.prefixPath.my.type   = "decorator"
form.foo.options.prefixPath.my.path   = "My/Form/Decorator/"
form.foo.options.prefixPath.my.prefix = "My_Form_Decorator"

// With an XML config:
<form>
    <element>
        <options>
            <prefixPath>
                <my>
                    <type>decorator</type>
                    <path>My/Form/Decorator/</path>
                    <prefix>My_Form_Decorator</prefix>
                </my>
            </prefixPath>
        </options>
    </element>
</form>

Example: Grouped Checkboxes

Now that you know how decorators work, how to write your own, and how to
tell your objects where to find them, let’s try it all out.

For our use case, we want to create a group of checkboxes referring to tags.
The checkboxes will be in an array structure two levels deep, with the first
level referring to a category, and the second being the checkbox value/label
pairs. We want to group each category of tags grouped in a fieldset with the
category name as the legend, and listed with the label trailing each
checkbox.

One other thing we want to do is ensure that the labels for each checkbox
are translated.

We’ll call our decorator something descriptive, like ‘CategorizedCheckbox’,
because we’re developers, and we’ll use our own creatively named class
prefix of ‘My_Form_Decorator’.

To start off, we need a registered element if we want to do anything, and
that element should extend Zend_Form_Element_Multi; that way we
know that getValue() will return an array. Additionally, we’re
going to make use of Zend_View view helpers, so we need to
ensure that a view object is registered with the element. Finally, we’ll
grab our translation object for later use.

class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        $element = $this->getElement();
        if (!$element instanceof Zend_Form_Element_Multi) {
            return $content;
        }
        if (null === ($view = $element->getView())) {
            return $content;
        }

        $translator = $element->getTranslator();
    }
}

Now that we have that out of the way, let’s do some work. First, we’ll
initialize the new content we want to create. Then we’ll grab our current
values so we can test to see if a given checkbox is checked later. Also, we
need to get the base name of the element so we can use it with the
individual checkboxes, as well as create our checkbox IDs. Finally, we can
start iterating over our options:

class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        // ...
        $html     = '';
        $values   = (array) $element->getValue();
        $baseName = $element->getName();
        foreach ($element->getMultiOptions() as $category => $values) {
        }
    }
}

Now we get to the fun part: generating the markup. Since fieldsets wrap
content, we’ll generate a list of checkboxes first, and then wrap them in
the fieldset. We can do this using view helpers. This is also where we will
translate our labels. Checkboxes need names and ids as well, so we’ll build
them here, just prior to passing them to our formCheckbox()
view helper. When all options are complete for a category, we’ll wrap them
in a fieldset, using the translated category as the legend.

class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        // ...
        foreach ($element->getMultiOptions() as $category => $values) {
            $boxes = '<ul class="checkboxes">';
            foreach ($values as $value => $label) {
                if ($translator instanceof Zend_Translate) {
                    $label = $translator->translate($label);
                }
                $boxName = $baseName . '[' . $value . ']';
                $boxId   = $basename . '-' . $value;
                $attribs = array(
                    'id'      => $boxId,
                    'checked' => in_array($value, $values),
                );
                $boxes .= '<li>'
                       .  $view->formCheckbox($boxName, $value, $attribs)
                       .  $view->formLabel($boxName, $label)
                       .  '</li>';
            }
            $boxes .= '</ul>';

            $legend = ($translator instanceof Zend_Translate)
                    ? $translator->translate($category)
                    : ucfirst($category);
            $attribs = array('legend' => $legend);
            $html .= $view->fieldset($category, $boxes, $attribs);
        }
    }
}

Finally, we need to return the content. We’ll assume that if no placement is
specified, we want to replace the content, and otherwise honor the
placement.

class My_Form_Decorator_CategorizedCheckbox extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        // ...
        $placement = $this->getPlacement();
        $separator = $this->getSeparator();
        switch ($placement) {
            case 'APPEND':
                return $content . $separator . $html;
            case 'PREPEND':
                return $html . $separator . $content;
            case null:
            default:
                return $html;
        }
    }
}

And that’s it! We can now set this as a decorator on an element, and we’re
done!

Other Ways to Customize Decorators

Most decorators allow a variety of options; you will need to href="http://framework.zend.com/manual/en/zend.form.standardDecorators.html">check
the documentation to determine what options are available. Many of these
options will allow you to customize the output, so providing values or
modifying them is an easy way to customize your forms.

You have several methods available for setting options:

  • setOption($key, $value) allows you to set a single option
    at a time
  • setOptions(array $options) allows you to set several
    options at once; $options should be an array of key/value
    pairs.
  • Finally, you can also pass an array of options or a
    Zend_Config object when adding a decorator to the element
    or form:
    // Pas 'tag' and 'class' options at construction time:
    $element->addDecorator('HtmlTag', array('tag' => 'div', 'class' => 'foo'));
            

View Helpers

Another way to modify your decorators, and specifically the ViewHelper
decorator, is to override your view helpers. Overriding view helpers with
your own custom view helpers is covered in a previous tutorial, and is a
viable option for customizing your forms as well.

In Conclusion

Decorators are simple classes that deliver complex functionality: generating
complex markup from your elements. Hopefully, with the information provided
in this tutorial, you can now see how to combine decorators and write custom
decorators to achieve custom markup to suit your site and application.

About Matthew Weier O'Phinney

Matthew is an open source software architect, specializing in PHP. He is currently project lead for Zend Framework, a project with which he has been involved since before the first public preview release. He is a Zend Certified Engineer, and a member of the Zend Education Advisory Board, the group responsible for authoring the Zend Certification Exam. He contributes to a number of open source projects, blogs on PHP-related topics, and presents talks and tutorials related to PHP development and the projects to which he contributes. You can read more of his thoughts on his blog, weierophinney.net/matthew/.

View all posts by Matthew Weier O'Phinney

49 Responses to “Decorators with Zend_Form”

  1. intellix Says:

    As someone who does both front and backend work I really hate Zend forms. Why am I being dictated how the HTML is produced in my frontend?

    This isn’t a separation of back and frontend. We’ve got validation in the backend which also wants to print HTML.

    Decorators look extremely complex and it doesn’t make sense without reading through a huge article.

    All I want is:

    <div>
    <?php
    echo $this->form->username['label'];
    echo $this->form->username['element'];
    ?>
    </div>
    <div>
    <?php
    echo $this->form->password['label'];
    echo $this->form->password['element'];
    ?>
    </div>

    But it seems even with the view decorator its still wrapping elements with HTML

  2. mirian01 Says:

    Hi,

    thanks for that great article!!! very helpful!
    I have a question about settings the prefix from the INI file. I tried it but nothing changed.. (the path was not included…) may I need some more declarations anywhere else??

    thanks,
    Miriam.

  3. thuyvn Says:

    thank you for very useffull article. I have read many your post, very easy to understand.
    Best Regards,
    Cao Thuy

  4. yasin792000 Says:

    I am not able to use VIEWSCRIPT method as described above. I am able to reslove it by following way:
    . Make model, view controller of a form like registration form
    . In view file (like index.phtml) instead of printing $this->form i used form elements like
    <form id="<?php print $this->escape($this->form->getName()) ?>" action="<?php print $this->escape($this->form->getAction()) ?>" method="<?php print $this->escape($this->form->getMethod()) ?>">
    <dl class="zend_form">
    <?= $this->form->nationalityId ?>
    <?= $this->form->zipCode ?>
    <?= $this->form->DOB ?>
    <?= $this->form->sex ?>
    <table border="0">
    <tr>
    <td><?= $this->form->firstName ?></td>
    <td><?= $this->form->lastName ?></td>
    </tr>
    </table>

    <?= $this->form->phone ?>
    <?= $this->form->currentPlaceofStudy ?>
    <?= $this->form->endofStudyId ?>
    <?= $this->form->educationId ?>
    <?= $this->form->jobCategoryId ?>
    <?= $this->form->maritalStatus ?>
    <?= $this->form->smoker ?>
    <?= $this->form->comments ?>
    <?= $this->form->newsletter ?>
    <?= $this->form->picture ?>
    <?= $this->form->next ?>
    <?= $this->form->cancel ?>
    <?= $this->form->userId ?>
    </dl>
    </form>

    I am not sure this is the right way to do. But it worked for me

  5. searthrowl Says:

    Hi,
    Firstly, thanks for the tutorial. Until I found it I was about to give up with Zend_Form completely!
    I’ve managed to do quite a bit now; but as usual have got stuck on one problem that doesn’t seem to want to be solved.

    I want to create form html from Zend_Config_Xml like:
    <form>
    <fieldset>
    <label for="a"><span> some text</span> <input type="text" name="a" id="a" /></label>
    …<!– lot more generated from a database –>
    <label for="doit"><input type="submit" name="doit" value="Submit" />
    </fieldset>
    </form>

    Hmm the problem is setting decorators, such that all label/inputs are wrapped in the label tag, and then the xml to set all label/input pairs to have the same structure, *and* all within the confines of Zend_Config. (Don’t want much do I ;)

    Ideally then I would then get:
    $form = new Zend_Form($SomeConfig);
    for ($i = 0 ; $i < count($rows) ; $i++){
    $form->addElement(new Zend_Form_Element_Text(‘row’.$i, array(‘label’ => $rows[$i]['text']));
    // Some more magic??
    }
    $form->addElement(new Zend_Form_Element_Sumbit(‘doit’, array(‘value’ = ‘Submit’));

    Or am I just plain naive?

    Simon

  6. umpirsky Says:

    This is super cool post. Thanks!

  7. vasilian Says:

    Great article! Exactly what I was looking for. Thank you

  8. vinnyman Says:

    Greetings all, and great tutorial. Now, how am I suppose to pass on my programmatic work to my designer without him cursing the default html code wrappings generated by Zend_Form? Who in g’s name uses <label>, <dt> and <dd> … I find having defaults here to be counter-intuitive, but that may just be me. Here’s what I propose to anyone in my situation:

    //create the form element
    $user_name = new Zend_Form_Element_Text(‘user_name’);
    //removing default decorators
    $user_name->removeDecorator(‘label’);
    $user_name->removeDecorator(‘htmlTag’);
    $user_name->removeDecorator(‘description’);
    $user_name->removeDecorator(‘errors’);

    So there.

  9. weierophinney Says:

    Not sure what is driving new readers to this article, but I have written
    several new articles on my blog that better outline how to use Decorators
    with Zend_Form, and do so in an iterative and easy fashion. Please, read these posts and comment on
    them if you still have questions.

  10. yuan.wang@danoo.com Says:

    elements.username.options.decorators.viewheloer.decorator = "ViewHelper"
    elements.username.options.decorators.htmltag.decorator = "HtmlTag"
    elements.username.options.decorators.htmltag.options.tag = "div"
    elements.username.options.decorators.label.decorator = "Label"
    elements.username.options.decorators.label.options.tag = "div"

  11. _____anonymous_____ Says:

    I agree with the other posts here about decorators being too complicated. They just make dealing with Zend_Form a real pain. Normal people will not use something that makes their life harder!

    This may follow a clever design pattern, but it is not elegant! (especially in PHP where it is littered with strings and arrays). Automatic form frameworks are supposed to save you time!!

    At the moment the benefits of using Zend Form are FAR outweighed by the cumbersome nature of this. Sorry, but IMO it is just horrible…

  12. lowtower1974 Says:

    Hello,

    maybe You can give me some help.

    I want to have something as proposed bei the YAMl team:
    http://www.yaml.de/en/documentation/css-components/form-construction-kit.html#c1385

    <div class="type-text">
    <label for="your-id">your label</label>
    <input type="text" name="your-id" id="your-id" size="20" />
    </div>

    I think, I am on a good way to get it right, but I have one problem.
    How can I give the surrounding div-tag a special class (‘errors’), only when an error occurs?

    I have this in my class TPL_Form that extends Zend_Form:

    $this->_standardTextDecorator = array(
    ‘ViewHelper’,
    ‘Errors’,
    array(‘Label’),
    array(‘HtmlTag’, array(‘tag’ => ‘div’, ‘class’ => ‘type-text’))
    );

    Appreciate any help,
    thanks,
    LowTower.

  13. kdford Says:

    Extend
    Zend_Form_Decorator_FormErrors
    -or-
    Zend_View_Helper_FormErrors

    I followed the example in this article to create a table-based form but having the errors appear under the input field, in the same table cell, was not what I wanted. I wanted the errors to be in another cell, to the right of the input field.

    I have achieved this so far in three ways
    – echoing the elements in the view script itself
    – modifying the Zend_Form_Decorator_FormErrors
    – modifying the Zend_View_Errors_FormErrors

    I was modifying those files just experimentally, I know ultimately I would extend Zend_Form_Decorator_Abstract and create my own class…

    My question is
    Which is the better method and why? I know that echoing the elements themselves in the view script gives ultimate flexibility, but since my form is relatively simple I felt using the decorators would be preferred.

    Thank you for your help

    Kevin

  14. bingomanatee Says:

    As far as I can tell if the overall rendering context is not itself a view, the ViewScript helper is completely skipped over.

    Because the ViewScript uses the form’s view’s partial method as an executor, and begins with an escape loop that kills out if the form’s view is not present, there should be a really big asterisk in this article’s ViewScript section that this only works inside the MVC.

  15. bingomanatee Says:

    I am currently in a project in which I am not using the MVC elements of ZF, but would like to use the form constructor with a custom ViewScript for the form (I have to lay out elements in a grid).

    How do I initialize the ViewScript’s base path location for scripts?

  16. bieleke Says:

    Hi,

    I have tried to use your style of giving another layout to some form elements.

    And i must say it works great, beside of one thing.

    public $elementDecorators = array(
    ‘ViewHelper’,
    ‘Errors’,
    array(array(‘data’ => ‘HtmlTag’), array(‘tag’ => ‘td’, ‘class’ => ‘element’)),
    array(‘Label’, array(‘tag’ => ‘td’)),
    array(array(‘row’ => ‘HtmlTag’), array(‘tag’ => ‘tr’)),
    );

    you use the above code to style the elements, an another to style the buttons. Now this works flawlessly and i was impressed, but what if you have a captcha into your form ?

    I’m already trying various things for the last three days, using your elementDecorators as an example for my own captchaDecorators, but i seem not be able to get this puppy going.

    I asked several people on the irc forum too, but none of them where that skilled with the captcha thing.

    Can you help me out ?
    Cause i have no clue how to fix this.

    Best Regards,

    Patrick

  17. ekoka Says:

    The last example seems to have an error. The $values variable inside the render() method of the CategorizedCheckbox decorator seems to refer to 2 different objects at the same time.

    - first:
    $values = (array) $element->getValue();
    // first reference

    - then a few lines later:
    foreach ($element->getMultiOptions() as $category => $values) {
    // second reference will overwrite first

    - within that first loop we have an inner loop:
    foreach ($values as $value => $label) {
    // which $values variable is this supposed to represent

    - and within that inner loop we have:
    $attribs = array(
    ‘id’ => $boxId,
    ‘checked’ => in_array($value, $values),

    );
    // confusing indeed

  18. lordspace Says:

    Hi again,

    ZF 1.6.1

    I would like to add one more thing:

    The form that is rendered using ViewScript decorator is accessible through "$this->element" instead of "$this->form".

    Therefore

    <?= $this->form->age ?>

    should be

    <?= $this->element->age ?>

    — a quote from API ref —
    Zend_Form_Decorator_ViewScript
    Description
    Description | Vars (details) | Methods (details) Constants

    Zend_Form_Decorator_ViewScript
    Render a view script as a decorator
    …..
    The view script is rendered as a partial; the element being decorated is passed in as the ‘element’ variable:

    1.
    // in view script:
    2.
    echo $this->element->getLabel();

    Slavi
    http://devquickref.com

  19. lordspace Says:

    Hi,

    in my case the view script should be in the view folder of the current action (action=forms)

    $form->setDecorators(array(
    array(‘ViewScript’, array(‘viewScript’ => ‘forms/demogForm.phtml’))
    ));

    Slavi
    http://devquickref.com

  20. sprynmr Says:

    $form->setDecorators(array(
    array(‘ViewScript’, array(‘script’ => ‘demogForm.phtml’))
    ));

    should be

    $form->setDecorators(array(
    array(‘ViewScript’, array(‘viewScript’ => ‘demogForm.phtml’))
    ));

  21. mascker Says:

    I just forgot to say on my previous comment that some people will find easier to create custom decorators, instead of defining one for each element.

    Regards,

  22. mascker Says:

    While I agree that form creation should be as simples as possible, I also think this was the best approach.

    The question we need to make is. Was it worthed?

    Zend_Form sure is a great piece of software, but most users will think twice if they have to spend almost the same time using Zend_Form or by just creating their own class using Zend_Validade and Zend_Filter

    I think Zend_Framewok needs a new component called Zend_DataFlow (or Data, the name doesn’t matter)

    Why not be able to integrate Zend_Db* with Zend_Form (already integrates Zend_Validate, Zend_Filter), Zend_Paginator. The goal is to provide users a interface for them to do in a few lines of code CRUD data from any source. Usually DB.

    I’m working with ZF for about 3 months, and I felt right away the need to do this.

    I’ve developed a new class called Bvb_DataGrid that receives simple arguments. As example a table output (could be XML, PDF, CSV, as it receives the results in a abstract way)

    $grid = new Bvb_Grid_Deploy_Table();
    $grid->from =’nr_clubes’;
    $this->view->grid = $grid->deploy();

    With only 3 lines of code I have a complete solution for CRUD with filtering, and sortable results. picture-> http://img95.imageshack.us/img95/2253/imagem1ew6.png (The names are in Portuguese but its understandable).

    Of curse you can extend it to use ajax, validators, filters, caching, joins, etc, etc.

    As note, if your database field is enum, the grid will present them as a select menu for filter. If you try to insert a letter on a Integer field, the system notifies you. (I will make this opensource within a few months, probably Nov)

    My point is, Zend_Form is a great component, the author really understand what he was doing, he has done it right, but the big picture is that Zend_Form should have a "father".

    I know that out of box we can start using it, but most forms are more linear that a label and a input. Date selectors, currency, image uploads, etc,etc. Wat if we want to alternate classes every element? We need to specify it for every element.

    With the new Zend_Dojo I think ZF 2 should have a component to handle all data cycle. But in a way that saves us time.

    To end my comment. I’m not criticizing the article or ZF, I’m just saying that we need a more wide solution. Zend_Form is designed the way its supposed to be, no arguments about that.

    Best Regards,

  23. sdevaux Says:

    Hello,

    first, thanks for this article, and I may apologize for my "French" english. :)

    I’m new to php, and to Zend Framework too, and I would like to do the following with my Zend_Form, composed by 6 inputs :

    <form>
    <table>
    <tr class="row1">
    <td>label & input11</td>
    <td>label & input12</td>
    <td>label & input13</td>
    </tr>
    <tr class="row2">
    <td>label & input21</td>
    <td>label & input22</td>
    <td>label & input23</td>
    </tr>
    <tr class="row3">
    <td colspan="3">submit input</td>
    </tr>
    </table
    </form>

    I begin with decorators, and ask me some "approach" questions :

    How can I do this kind of presentation, depending on the iterating on form elements (in the previous example, wrapp with a <tr>…</tr> only every 3 inputs)
    1. Do I have to create my own form extending Zend_Form ? The render method would do the stuff wtih the foreach iteration ? (the disadvantage may be that I will have to entirely rewrite the render method, and so I may break the ascending compatibility (am I right ?))
    2. Can this be done only with decorators on elements, rather than on form ?
    3. use 2 subform with 3 input each, and each subform would have a "TRdecorator" ?
    4. others ways ?

    I don’t want to use viewscript, because my final goal, is to generate forms, which presentation (n rows, m data cells per row) should be dynamic, not fixed

    I hope this is clear enough, and I am aware to all advice

    Thank in advance
    Sylvain

  24. flaviohirasawa Says:

    I really liked the idea of having all the configuration of the form in an INI file, but I’m wondering if it’s possible to have the decorations set also in the same INI file. I’m doing something like this, but it’s not working.

    user.login.elements.username.decorators.htmltag.tag = "td"
    user.login.elements.username.decorators.htmltag.class = "login"
    user.login.elements.username.decorators.label.tag = "th"

    What am I doing wrong?

  25. MaloWatt Says:

    Greetings!

    Trying to create a custom decorator, have a similar problem to the one discussed earlier – an exception "Zend_Loader_PluginLoader_Exception’ with message ‘Plugin by name My_Decorator_Label was not found in the registry".

    Form class:

    require_once ‘My/Decorator/Label.php’;

    class MyForm extends Zend_Form
    {
    public function __construct($options = null)
    {
    $options['prefixPath'] = array(Zend_Form::DECORATOR => array(‘prefix’ => ‘My_Decorator’, ‘path’ => ‘My/Decorator/’));
    $options['elementPrefixPath'] = array(Zend_Form::DECORATOR => array(‘prefix’ => ‘My_Decorator’, ‘path’ => ‘My/Decorator/’));

    parent::__construct($options);

    $this->addElement(‘text’, ‘title’, array(‘label’ => ‘Title’))
    ->setDecorators(array(‘ViewHelper’,
    ‘Errors’,
    array(array(‘data’ => ‘HtmlTag’), array(‘tag’ => ‘div’, ‘class’ => ‘a’)),
    array(‘My_Decorator_Label’, array(‘tag’ => ‘div’, ‘class’ => ‘b’)),
    )
    );
    }

    public function loadDefaultDecorators()
    {
    $this->setDecorators(array(
    ‘FormElements’,
    array(‘HtmlTag’, array(‘tag’ => ‘div’, ‘id’ => ‘wrapper’)),
    ‘Form’,
    ));
    }
    }

    Where did I gone wrong?

  26. mightyi Says:

    This tutorial has really helped me, Zend forms seem to be quite complicated to learn but I’m hoping that the effort will be worth it once it’s there. We need to see more examples of ordinary users posting their scripts with demo’s /examples and comments in the forums.

    Typo in line 27 of categorizedCheckbox class, not particularly hard to find/fix though.

    Also this might help someone, or at least save them 5 minutes. How to add the categorizedCheckbox to an element
    <?php
    $form->multiCheckboxElement->setDecorators(
    array(
    array( ‘categorizedCheckbox’ ) ,
    )
    );
    ?>

  27. virgilio_quilario Says:

    hi,

    here is the correct way to specify a viewScript decorator:

    $form->setDecorators(array(array(‘viewScript’, array(‘viewScript’ => ‘demogForm.phtml’))));

    here is the correct path for the script:

    application/views/scripts/demogForm.phtml

    I have a question to the article author.
    Where in the framework file structure should we best put the custom form class?

    thanks,

    virgil

  28. aumann Says:

    Thank you very much for your brilliant article and for the work done on Zend_Form! Unfortunately, I cannot get your example of using the ViewScript decorator for full customization running. I always get the warning

    No view script registered with ViewScript decorator in /local/ZendFramework-1.5.1/library/Zend/Form.php on line 2179

    I thought the view script itself would be searched for in application/views/scripts/<controller>/. Yet the warning is issued whereever I put it. Also: where do I have to place my setDecorators()? I have a custom form that extends Zend_Form as in your My_Form_Registration example. But no matter where I try

    $this->setDecorators(array(array(‘ViewScript’, array(‘script’ => ‘demogForm.phtml’))));

    the warning still remains. Any help would be much appreciated!
    -Stefan

  29. binxter Says:

    Sometimes design pattern theory doesn’t transpose to elegant real-world use. This is rather unwieldy in my opinion for forms. Still it’s a great write up on this. Also the best thing about ZF is you only need to use the modules you want to use!

  30. weierophinney Says:

    @pysan: please direct further questions to the fw-mvc mailing list, or the
    #zftalk IRC channel.

  31. weierophinney Says:

    @tompercival: Aaargh; wrong variable. Use $this->element, not $this->form.

    The ViewScript decorator doesn’t care what type of object it’s passing to
    the view script, and so uses ‘element’ as the variable (which is analogous
    to the decorator’s getElement() method). It also passes ‘content’ and
    ‘decorator’ — the original content provided to the decorator, and the
    decorator itself.

  32. pysan Says:

    OK, so I used the <code>$element->disable = true;</code> and it worked great. That is, until I tried to submit the form w/ other errors, in which case it wiped out the values in those fields with the disable property set. Even when I reset those values on failure in my controller with <code>$form->populate()</code> the errors still persist, and won’t allow the form to complete?

  33. tompercival Says:

    @weierophinney

    Great article. Thanks. But I seem to be having the same issues as @pysan.

    When I attempt to render a form in a ViewScript using the php you suggest I get:

    Fatal error: Call to a member function getName() on a non-object in /path/to/template/template.phtml on line 2

    Using the code:

    <?= $this->form($this->form->getName(), false, array(
    ‘action’ => $this->form->getAction(),
    ‘method’ => $this->form->getMethod(),
    )) ?>

    Do you have any suggestions?

    With thanks

    Tom

  34. weierophinney Says:

    For those wondering what the difference is in @gammamatrix’s corrections, he
    fixes a missing end paren in the $elementDecorators property.

  35. weierophinney Says:

    @pysan: To access the form’s data via a view script, use its accessors:

    Render the form opening tag:
    <?= $this->form($this->form->getName(), false, array(
        'action' => $this->form->getAction(),
        'method' => $this->form->getMethod(),
    ) ?>
    

    If you have set particular metadata, use getAttrib() to
    retrieve it.

  36. gammamatrix Says:

    Oops, I made an error in my correction:

    public $elementDecorators = array(
    ‘ViewHelper’,
    ‘Errors’,
    array(array(‘data’ => ‘HtmlTag’), array(‘tag’ => ‘td’, ‘class’ => ‘element’)),
    array(‘Label’, array(‘tag’ => ‘td’)),
    array(array(‘row’ => ‘HtmlTag’), array(‘tag’ => ‘tr’)),
    );

    This is the correct $elementDecorators variable in My_Form_Registration

  37. gammamatrix Says:

    Excellent article, thanks for taking the time to write it!

    The extended class My_Form_Registration has a few errors. Here is a corrected version:

    class My_Form_Registration extends Zend_Form
    {
    public $elementDecorators = array(
    ‘ViewHelper’,
    ‘Errors’,
    array(array(‘data’ => ‘HtmlTag’), array(‘tag’ => ‘td’, ‘class’ => ‘element’)),
    array(‘Label’, array(‘tag’ => ‘td’),
    array(array(‘row’ => ‘HtmlTag’), array(‘tag’ => ‘tr’)),
    )
    );

    public $buttonDecorators = array(
    ‘ViewHelper’,
    array(array(‘data’ => ‘HtmlTag’), array(‘tag’ => ‘td’, ‘class’ => ‘element’)),
    array(array(‘label’ => ‘HtmlTag’), array(‘tag’ => ‘td’, ‘placement’ => ‘prepend’)),
    array(array(‘row’ => ‘HtmlTag’), array(‘tag’ => ‘tr’)),
    );

    public function init()
    {
    $this->addElement(‘text’, ‘username’, array(
    ‘decorators’ => $this->elementDecorators,
    ‘label’ => ‘Username:’,
    )
    );
    $this->addElement(‘text’, ‘firstname’, array(
    ‘decorators’ => $this->elementDecorators,
    ‘label’ => ‘First Name:’,
    )
    );
    $this->addElement(‘text’, ‘lastname’, array(
    ‘decorators’ => $this->elementDecorators,
    ‘label’ => ‘Last Name:’,
    )
    );
    $this->addElement(‘submit’, ‘save’, array(
    ‘decorators’ => $this->buttonDecorators,
    ‘label’ => ‘Save’,
    )
    );
    }

    public function loadDefaultDecorators()
    {
    $this->setDecorators(array(
    ‘FormElements’,
    array(‘HtmlTag’, array(‘tag’ => ‘table’)),
    ‘Form’,
    ));
    }
    }

  38. pysan Says:

    Thanks for the answers to my previous questions, now I ahve another one.

    So I have a custom viewscript setup, and is being pulled in properly, but I can’t seem to find the right scope to pull the variables into the page. If I use print_r($this); I can pull a whole gigantic array of arrays, showing my data, but I’ve tried $this->form and all kinds of various methods but no other way I’ve tried has anything in it?

    So I have a custom_form that extends zend_form, and have the following method for the viewscript:

    public function loadDefaultDecorators()
    {
    $this->setDecorators(array(
    array(‘ViewScript’, array(‘viewScript’=>’_demogForm.phtml’))//, ‘script’ => ‘demogForm.phtml’))
    ));
    }

    What way should I be accessing my form’s data to print it in my custom viewscript?
    -Ryan

  39. weierophinney Says:

    @erflynn: Actually, the description decorator can utilize arbitrary HTML if
    you set the decorator’s ‘escape’ option to a boolean false value. You can do
    this in a couple ways:

    // At decorator instantiation:
    $element->addDecorator('Description', array('escape' => false));
    
    // Pulling it out and modifying it:
    $decorator = $element->getDecorator('Description');
    $decorator->setOption('escape', false);
    

    It’s probably easiest to do non-HTML text, but the above makes it possible
    to use HTML markup.

  40. weierophinney Says:

    @pysan: thanks!

    Regarding 1) and 2), DevZone is not currently using Zend_Form; it’s still
    using Zend Framework 1.0 at this time. You would need to create a custom
    decorator to do what you are suggesting.

    Regarding 3), you’d need to provide a custom description decorator in order
    to create two tags around the description. This would be trivial to
    accomplish.

    Regarding 4), you can lock fields by setting their ‘disable’ property to a
    boolean true value: $element->disable = true; All elements have
    this capability, as it is built into the various form view helpers.

  41. erflynn Says:

    I looked at the Description decorator, but it looks like it’s more appropriate for a single line of text, whereas I want to inject arbitrary HTML. So, I could write a Decorator to do this.

  42. justinwoods Says:

    Thanks for the great article.

    I disagree with the above posters who say that the form decorator system is too complicated. I think it strikes a great balance in that it provides the power to achieve practically anything you want to do with a form, and it’s all based on a few easily extensible classes. It also provides levels of customizability so that simple things are accomplished simply.

    And hey, you don’t have to use it if you don’t want to! :D

  43. pysan Says:

    Thanks for all the work this must take trying to explain your great work! I really like Zend_Form, and do agree that it makes form creation much quicker, and have already used it to create a number of forms on a new site I’m creating. However, I keep hitting my head on a ceiling I’m sure I put above myself, and can’t seem to find out how to break through. This article helped a bit, but I’ll just ask here the several questions I have been pouring over google for answers to.

    1) First, how does Zend’s login on this site accomplish getting all the errors to compile together in their nicely formatted box at the top in class="error-message", in place of class="welcome-message".

    2) How do I get the requiredSuffix, ‘*’ to look like Zend’s login form with <b class="req">*</b> instead? I tried it just earlier this morning before I looked at Zend’s code, and it output the literal html (&lt; and &gt;).

    3) Finally, how do I get the description to work well in a table, such as your example in this article, but adding a third column to the right where the field’s description will be displayed, but a blank <td></td> will be created if there is not a description? I tried this several ways, and mine does output the correct tags using the decorators, but only for those fields that have descriptions, such as below.
    <tr>
    <td><label for="username" class="myUsernameClass required">Username *</label></td>
    <td class="element"><input type="text" name="username" id="username" value="" class="myUsernameClass"></td>
    <td class="txtDescription">Username must be AlphaNumeric and between 6-20 characters.</td>
    </tr>

    4) How do I show a form with some of the fields showing only values? The use case for this is after signing up, the edit page, I typically will lock some fields so that the user can’t edit them, such as the username and join date, but I do want to display them. But so far I can’t figure out whether I need to use decorators or some other method to accomplish this.

    Other than those quick questions, this has made things sooo much easier than the old way. Now I just need to add in some JS client-side validation, and it will be smokin’!
    -Ryan

  44. weierophinney Says:

    Currently, we already have a CSRF-protection element, called ‘Hash’
    (Zend_Form_Element_Hash), available in core. We have plans to add captchas
    as well for the 1.6 release (you can see the proposals in the ZF wiki).

  45. weierophinney Says:

    Actually, adding text (or HTML) above or below an element is trivial.
    Zend_Form_Element::setDescription() allows you to set an arbitrary
    description to associate with the element; you can then attach the
    Description decorator to your element, and specify a placement of ‘append’
    or ‘prepend’ to determine where it shows up. Unfortunately, this is
    currently not added by default, which means you have to set the decorator
    manually; we plan to have it enabled by default for the 1.6 release.

  46. joostpluijmers Says:

    I just wanted to say that I really apreciate the effort being put into the more "frontend" technologies. Additions like this really enlarge the userbase of the Zend Framework.

    I daily develop modules in the Drupal enviorment where they also implement some sort of form rendering system. Drupal is an older project so it has more contributed modules like captcha’s and automatic token validation.

    I would like to ask if there are plans for security oriented form validators? Just so I don’t start unnessesary projects.

    Kind regards.

  47. erflynn Says:

    Automatic form handling is a good idea, in general. It removes the grunt work, as yacc or bison does for compilers or regular expressions for text extraction.

    This article was quite helpful in shedding some light on Zend_Form and decorators. I see that Zend_Form decorators are analagous to the Decorator pattern typically used in GUI frameworks. However, I think this solution is unwieldly for form elements. All I want to do is add a snippet of HTML before or after an element. Perhaps an option to include a string with placeholders would help, or "suffix" and "prefix" attributes on the element.

  48. weierophinney Says:

    You can use Zend_Form right away without doing any of the stuff in this
    article; the default decorators shipped with ZF are perfectly serviceable
    for the majority of forms.

    However, there are times when you need to have more custom output, or you
    need to be able to hand off the form to your designers. The techniques in
    this article are aimed towards those users who find themselves in such
    situations.

    Sure, your solution may seem less complex, but is it generalizeable
    to the plethora of needs presented by an audience the size of Zend
    Framework’s user base? It was precisely for this reason that we wanted to
    make something immediately useable, but also flexible and customizable, and
    decorators allow for this. The concept is actually very simple, but it’s
    also very abstract, which leads to articles such as this one — which is
    aiming to educate users who are struggling with the abstract concept.

    You ask if this stuff saves time or makes the code better. My answer is that
    yes, it does. I’ve applied the ideas a number of times, and can usually come
    up with custom, repeatable content very quickly and with more consistent
    results than when coding the forms by hand. I have anecdotal evidence from a
    number of users who have the same experience — that they have been able to
    create full-featured forms with validation, filtering, and decorators –
    more quickly than when using their previous solutions.

    Lastly, learning a new tool is often a time investment. In the past, I have
    eschewed frameworks as taking too long to learn, and making things more
    difficult. However, if you take the time to learn a framework or a new tool,
    you will often find that the time spent saves you much more time the longer
    you use it. Sure, you could have coded a form or two while reading the
    article — but how many forms can you code and customize now? ;-)

  49. _____anonymous_____ Says:

    Over 30,000 characters in that article. My latest bit of dev work involves forms for logging in, creating projects, modifying projects, adding and editing users and updating inventory and I can create all of those forms (hook them up to validation, include them in pages, create the code to process them, etc.) in the time it took to read this article on decorators. It IS powerful stuff but does it really save time or make the code better? I’m not trying to slam this work, I’m just trying to "get" it and it seems like overkill to me.

    I may be a simpleton (distinct possibility of that being true) but I just don’t see the use case to which this would practically apply. Prove me wrong (please)!