September 23rd, 2009

A Zend_Form is (or at least, can be) an unfortunate mish-mash of logical and visual rules. Instead of designing how the form will render visually using HTML in a .phtml file, like everything else that’s rendered, you have to jump through a bunch of programmatic hoops to set up the visual properties of the form in code using decorators. This is a little rich for my taste, so I use a different method.

The hoop jumping isn’t so bad if the layout of the form is simple, for example a basic layout with all the labels to the left and all the elements on the right. But to create a complex form layout, you probably want to write it in HTML anyway, and then back-fit it into decorator code by breaking the structure apart and assigning different pieces to individual elements of the form. Maybe you like to do this, that’s fine. I don’t, which, I think, is also fine. (And, kind of sane.)

To me, this back-and-forth decorator process is tedious, and it’s twice the amount of work to get the same end result (a rendered form). Plus, to change something visually, you either have to modify your original HTML template and then back-fit it again, or you mess with the decorators directly without the benefit of the template. The latter being the “hope for the best” method. It’s kind of frustrating, and one too many levels of indirection.

So in short, mashing this visual structure into the form object itself:

  • Encroaches on the role of the view.
  • Breaks separation of concerns.
  • Is harder than it should be.

The hybrid approach to Zend_Form is to use all of its functionality except the decorators for visual layout. Instead, you just lay the form out normally in a view script, and render each element individually where it needs to be.

This process provides the best of both worlds. The form object is still used to define the and populate elements and do validations, but the actual layout is done in HTML, which is easier to create, modify and maintain.

Here’s an example, with bits of a form object, controller, and view script:

MyForm.php:

class MyForm extends Zend_Form
{
    public function __construct($options = null)
    {
        parent::__construct($options);
        $this->setName('my_form');

        $textFilters = array(
            'StripTags',
            'StringTrim'
        );

        $options = array(
            'name' => 'name',
            'required' => true,
            'validators' => array('NotEmpty'),
            'filters' => $textFilters,
            'class' => 'form',
            'size' => 65,
            'required' => true
        ); 

        $input = new Zend_Form_Element_Text($options);
        $this->addElement($input);

        $options = array(
            'name' => 'age',
            'required' => false,
            'validators' => array('NotEmpty'),
            'filters' => $textFilters,
            'class' => 'form',
            'size' => 5,
            'required' => true
        ); 

        $input = new Zend_Form_Element_Text($options);
        $this->addElement($input);

        $element = new Zend_Form_Element_Submit('submitbtn');
        $element->setAttrib('id', 'submitbtn')
                      ->setAttrib('class', 'button')
                      ->setLabel('Save');
        $this->addElement($element);

        $this->setElementDecorators(array(
            'ViewHelper',
            'Errors'
         ));
    }
}

MyController.php:

public function showAction()
{
    $form = new MyForm();
    $form->populate($this->_request->getPost());
    $this->view->form = $form;
}

show.phtml:

<form
name="<?php echo $this->form->getName() ?>"
id="<?php echo $this->form->getName() ?>"
method="post" enctype="application/x-www-form-urlencoded"
action=""
>
<table>
<tr>
<td><?php echo $this->form->getElement('name')->getLabel() ?></td>
<td><?php echo $this->form->getElement('name')?></td>
</tr>

<tr>
<td><?php echo $this->form->getElement('age')->getLabel() ?></td>
<td><?php echo $this->form->getElement('age')?></td>
</tr>

<tr>
<td></td>
<td><?php echo $this->form->getElement('submitbtn')?></td>
</tr>

</table>
</form>