Skip navigation.
Home
That which cannot be rendered in binary is by definition a delusion
 

Reply to comment

Zend Forms: my, how they blow!

Ultimately anyone who comes into the ZF runs into forms and is blown away by how inflexible difficult the forms are to use. While they do have a fair amount of power, the investment you put into learning them doesn't seem to be in proportion to the functionality you get out of them.

To be fair they do bring validation do the table, as well as the ability to drive their content through config files. But the price of that is a series of decorator classes that nest like Russian dolls. And if you think that sounds cool, get a set of russian dolls and try and switch out the third and fifth doll.

And while the decorator pattern can be defended as a design choice that just takes some getting used to the fact that the form module can only be used (out of the box) within a MVC view is fairly baffling. There is no natural reason why forms should not be able to render their markup without the assistance of some fairly shallow view helpers.

This is a "known fact" in the ZF world.

If they suck so much, why do you use them?

That being said, I use them. I use them for three reasons:

  • I prefer to drive repeated patterns of markup parametrically. Instantiating forms through ini files works for me. In fact, the latest project I have been working on would be nearly impossible without the ability to generate forms parametrically.
  • I like the practice of binding the input elements and the validation into one package. In fact I generally bind the loading and saving of record objects through the form objects.
  • I ultimately find that decorators and view objects are the best way to keep a series of form objects consistent across an application without committing yourself to maintaining a swath of distributed hand code.

Plan B: View helpers

If insulating yourself from the tedium of markup is your only concern, use the view helpers for form elements. I emphasize this because when you get to ZF and see Zend_Form, its easy to assume you have bought into the form library; you have not! Your only buy in (if you have any at all is that using Zend_Form locks you into Zend_View -- as do the view helpers for forms.

You can still use validation -- the validation module is decoupled. The only thing you are oblicated to do is handle the popoulation/error display round trip which really is not much of a burden.

And of course if you use Dojo you should be developing your forms as Dojo objects anyway not binding them to your back end! While you can create Dojo Forms using markup, you are denying yourself the opportunity to take another little load off the server by using a pure JavaScript library for your Dojo widgets and cutting yet another transmission out of the loop in repeated views of the same page. (This of course implies you are willing to learn Dojo. Trust me: you are. you are.)

So before you dive into Zend_Form, ask yourself: is this trip really necessary?

  •  If you are going to use Dojo driven forms, use Dojo
  • If you want a wildly custom form, you are really better off using the view layer and old school HTML
  • If the only thing attracting you to Zend_Form is built in Validation, remember: you don't need it! Validation works quite well without being coupled to a form object.

Zend_Form couples validation, config-driven forms, and a very rigid but ultimately useful set of interlocking render components. It makes you completely dependant on your engineers for something which is fundamentally a view element that your designers may very well want a strong hand in developing and maintaining.

You were warned.

Still reading?

Why? What part of Zend Forms Blow do you not get? Well I am sure you have your reasons, so here is a great little analysis of Zend Forms with every sucky little detail spelled out for you. Well not every detail. The Zend documentation does that. But here are the COOL details.

Here is the VERY LEAST AMOUNT OF INFORMATION you need to use Zend Forms.

Zend Forms are based on the Zend_Form class. You can extend this class, but note, its NOT abstract: its legitimate to just create a new Zend_form(), add elements to it, and set its properties.

Zend Forms contain an ordered list of form elements. These are objects that extend from Zend_Form_element and mirror most common form elements (radios, text areas, text, etc.).

You can create an element using the classic new [classname] constructor, then seed them into the form one by one using addElement. However, its a lot easier to pass a configuration (see below) into the constructor and do it all at once.

When you are done populating the form (and setting its target action) you can render it out via "echo $myform"; because it has a __toString method, the "String form" of a Zend_Form object is an HTML form.

$form = new Zend_Form();

$name = new Zend_Form_Element_Text('name');
$name->setLabel("Username");
$name->setRequired(TRUE);

$blood_type = new Zend_Form_Element_Radio('blood_type');
$blood_type->setMultiOptions(array('a+'=> 'A Positive', 'b-' => 'B Negative', 'c#' => 'C Sharp'));
$blood_type->setRequired(TRUE);

$submit = new Zend_Form_element_Submit();
$submit->setLabel('Log In To Vampire Douchebags.com');

$form->addElement($name);
$form->addElement($blood_type);
$form->addElement($submit);
$form->setAction('/user/login.php');
$form->setMethod('post');

echo $form; // renders markup

When you are writing a responder to recieve data from a Zend_Form you can use the isValid(); method to do two things at once:

  1. Populate your form elements with values from a name-keyed hash
  2. Determine whether the data in your value hash meets the validation requirements of the form.

In the above case, both name and blood_type are required. Note that "Validation" in the form context is a very "blunt" sort of concept; it considers only the existence and format of input. True validation (against a database, or other advanced criteria) is still your responsibility (though lots of systems, including Authenticate and Authorize, exist in ZF to help you out there too.)

Each form element can be qualified with one or more decorators; for instance, an e-mail field will most likely get paired with an email validation filter. Also many fields will be marked as required if a non-empty value is necessary for the application to proceed. Note that extra data is not a problem for Zend_Form, so, for instance, presuming that you repeated the above code inside /users/login.php

if ($form->isValid($_REQUEST))

or

if ($form->isValid($this->_getAllParams())

are legtimite ways of testing your input; that is you don't have to filter out the non-form related data from your input array.

isValid is, of course, a boolean function. If you have no criteria at all on your form it is still useful, in that it populates your form, allowing you to do things like:

$sql = sprintf('SELECT * from users WHERE name like"%s" AND blood_type LIKE "%s";',
     myAntiInjection($form->username->getValue()),
     myAntiInjection($form->blood_type->getValue()));

Note that once a form has an element, you can retrieve it (magically) by name from its containing form.

This is ALL YOU NEED TO KNOW about forms:

Create a form

  1. Create field elements, and their options
  2. Make Field Elements
  3. Set their validation criteria and labels
  4. Add them to the form
  5. render the form
  6. retrieve the data from a form submission
  7. Validate the data
  8. use the data for your own purposes

There is a lot left out, of course. For one thing the appearance of the form is pretty much crammed down your throat, though there are plenty of CSS hooks for you to work with in the rendered markup. The fix for that is to learn how to use Decorators and to use them well. That gets past the "Very least you need to know. So you can stop reading here.

For the love of god, why are you still reading?

Okay here is the stuff you need to know to de-blow Zend Forms. Its hard core and pretty rough to chew on but you need to learn it if you are committed to using Zend Forms in your application. First, the overall object UML. Not shown are the Validation classes, which are not technically part of Zend_Form but are their own library branch.

Zend forms are overamped collections of elements -- each mapped to a traditional HTML form object. They are also collections of decorators. Elements themselve are also collections of decorators. Decorators produce the visual aspects of the form and elements.

But first you need to learn the "Right" way to make a form; in my experience this is ALWAYS with a config file. The Zend_Config library transforms configuration files into an object tree(or nested array) that when passed in a Zend_Form's constructor will create all your elements at once. This is a great way to expose your form's features to other developers so they can go in and tweak your fields and their labels with relative ease.

elements.state.type = "text"
elements.state.options.validators.strlen.validator = "StringLength"
elements.state.options.validators.strlen.options.min = 0
elements.state.options.validators.strlen.options.max = 255
elements.state.options.required = false
elements.state.options.label = "State"

elements.country.type="select";
elements.country.options.required = true
elements.country.options.label="Country"


elements.postalcode.type = "text"
elements.postalcode.options.validators.strlen.validator = "StringLength"
elements.postalcode.options.validators.strlen.options.min = 0
elements.postalcode.options.validators.strlen.options.max = 255
elements.postalcode.options.required = false
elements.postalcode.options.label = "Postal"

; description element

elements.notes.type = "textarea"
elements.notes.options.cols = 40;
elements.notes.options.rows = 3;
elements.notes.options.required = false
elements.notes.options.label = "Notes"

; submit button

elements.submit.type = "submit"
elements.submit.options.label = "Add Place"

and load them either as a parameter to the "new" method

$config = new Zend_Config_ini('form_config.ini');

$f = new Zend_Form($config);

or embedded in the constructor

class Zupal_Places_Form
extends Zend_Form
{
    public function __construct(Zupal_Places $pPlace = NULL)
    {       
        $ini_path = dirname(__FILE__) . DS . 'form.ini';
        $config = new Zend_Config_Ini($ini_path, 'fields');

        parent::__construct($config);
    }

...

}

Fair Warning: you always have to pass control through to the Zend_Form constructor or your form won't render -- it may be a personal failing but I've repeatedly found in my enthusiasm to customize the constructor I often fail to remember this (because I leave out the parent::__construct() passthrough) and end up staring at a blank form. Once you have run the parent (Zend_Form) constructor your elements are available for customization. 

Zend Validation

Note also, in passing, I've shown you the pattern for the validation of a zend form element: the state and postal code elements have stringlength validation. Note that unless you are using Zend_Dojo (which you should), just adding validation to your form will NOT DO ANYTHING except to alter the way $myForm->isValid($_REQUEST) reacts to your submitted data. So adding validators to a form without calling isValid on the submitted data will not have any effect: Zend_Form does NOT have implicit client side filtering. (as mentioned, though, Zend_Dojo_Form DOES have client side filtering, which is beyond this articles scope.)

Customizing the Appearance of a Form

The form renders by default in a definition list with the terms in dt tags and the elements in dd tags. This system is in general fairly easy to format using targeted CSS, but its not too rare to need to replace this pattern with custom markup -- say, to render the form in a table.

Some background for those too young to have seen a definition list <dl><dt><dd> in action before: these tags were originally designed to produce a "Glossary" type listing: here they are in action. Definition lists fall under the same general school as ordered/unordered lists (<ol><li>... <ul> <li>).

Democrat
A tax-crazy do-nothing who claims to be for the little guy but who uses his office to line his own pockets.
Republican
See Above

 

You may be asking yourself, what does a glossary have to do with a web form. The answer is, well, nothing really; however it does obeay the general topography of a form, that is, name/value blocks within a vertical list, so it provides handy "hooks" for contextual CSS to respond to. Classically, you'd put a form in a table with <th> tags around the label and <td> tags around the fields, and I often do just that. However because the Pope has declared that "Tables are the new Jews" its a lot more common to use CSS to make a definition list behave like a table, so thats what you're getting.

One disturbing note: even hidden elements are put in dt/dd tags creating unsightly gaps in your form! Because of this -- if you are in a hurry -- put all your hidden elements at the end of your form.As a third alternative, you can use a quick regex to elimate all empty titles: preg_replace('~<dt>&nbsp;</dt>~', '', $form->render()).

Most often though you will find that the Glossary format is not serving your need and will want to change the tags that encase your form. This is done by butchering the decorators and generally means you'll be writing your own. While it is sometimes possible to reuse and repurpose the stock decorators, its often easier to just use a custom decorator.

Here is a form that uses a custom decorator around elements. Specifically, a (gasp!) Table Row:

$writer = new CPF_Formset_FormDef($this);
$config = new Zend_Config_Ini('form_def.ini');
$form = new Zend_Form($config);
$form->removeDecorator('HtmlTag');

foreach($form->getElements() as $element):
    $view = $element->getDecorator('ViewHelper'); // preserve the one useful decorator, 
       // that is the one that actually writes the <input> (or similar) markup. 
    $element->clearDecorators();
    $element->addDecorator($view); //... so we put it back in after the others are gone. 

    if !$element instanceof Zend_Form_Element_Hidden):
        continue;
    endif;
    $element->addDecorator('row'); // our custom decorator
endforeach;

return $form;
   
// the row decorator

class Decorators_Row extends Zend_Form_Decorator_Abstract
{
    public function render($content)
    {
        $cpf_field = $this->getElement()->getAttrib('cpf_field');
        ob_start();
?>

<? if (($cpf_field) && strcasecmp($cpf_field, 'new')): ?>
<tr>
    <th><?= $this->getElement()->getLabel() ?>
    <? if ($this->getElement()->getDescription()) : ?>
    <br /><small><?= $this->getElement()->getDescription() ?></small>
    <? endif; ?>   
    </th>
   
    <td><?= $content ?></td>
</tr>
<?
        return ob_get_clean();
    }
}

// The View Script

// rendering the form: note the form decorator is removed and rendered as markup in order to put
// the table tags around the form inside the form tag. This could probably be accomplished with
// better uses of decorators.

<form method="post" action="/formsetjob/editvalidate">
    <table width="300px">
    <tr>
        <th width="150">Field</th>
        <th>Proposal Value</th>
        <th width="200">Form Value</th>
        <th>Status</th>
    </tr>
<?
$this->form_entry->removeDecorator('Form');
$form_markup = $this->form_entry->render();
echo $form_markup;
?>
    </table>
</form>

Seem like a lot of work just to customize the appearance of a form? I heartily agree. If you want to create a totally custom markup around a form, say, arranging a series of elements in a N x N matrix, you are even worse off, and should probably skip the whole form library.

Seeding and Retrieving Form Data

The most common form scenario is editing and updating a specific record This requires you to seed the form elements with values from a rowset. Fortunately elements can be accessed by name either using magic accessors or the getElement() method. Presuming your seed data is accessible as an array, you can simply seed the data iteratively:


foreach ($row_data as $field => $value)
{
    $form->getElement($field)->setValue($value);
}

You can seed the get/post values back into the form as such:

if ($form->isValid($_REQUEST) // ignores values not in form
{
    foreach ($old_record as $f => $value)
    {
        $old_record[$f] = $form->getElement($f)->getValue();
    }
} else { 
// handle bad data
}

$my_table_handler->save($old_record);

  I often internalize this process in a custom form definition:

class Zupal_Places_Form
extends Zend_Form
{
    public function __construct(Zupal_Places $pPlace = NULL)
    {        
        $ini_path = dirname(__FILE__) . DS . 'form.ini';
        $config = new Zend_Config_Ini($ini_path, 'fields');

        parent::__construct($config);
        
        $this->load_countries();
        $this->setMethod('post');

        if (is_null($pPlace)) {
            $pPlace = new Zupal_Places();
            $this->set_place($pPlace);
        }
        else
        {
            $this->set_place($pPlace);
            $this->place_to_fields();
        }
    }

/* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ load_countries @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ */
    /**
    *
    * @return void
    */
    public function load_countries ()
    {
        $this->country->setMultiOptions(Zupal_Places_Countries::as_list());
    }

/* @@@@@@@@@@@@@@@@@@@@@@@@@@ place @@@@@@@@@@@@@@@@@@@@@@@@ */

    private $_place = null;
    /**
     * @return Zupal_Places;
     */
    public function get_place() {
        if (is_null($this->_place))
        {
            $this->_place = new Zupal_Places();
            $this->fields_to_place();
        }
        
        return $this->_place;
    }

    public function set_place($pValue) { $this->_place = $pValue; }

/* @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ place_to_fields @@@@@@@@@@@@@@@@@@@@@@@@@@@@@ */

    /**
     * Note -- to prevent recursion this method does NOT check the existence of _place.
     */
    public function fields_to_place()
    {
        $this->get_place()->setAddress(array($this->addr->getValue(), $this->addr2->getValue()));
        $this->get_place()->set_name($this->name->getValue());
        $this->get_place()->setCity($this->city->getValue());
        $this->get_place()->setState($this->state->getValue());
        $this->get_place()->setCountry($this->country->getValue());
    }
    
    /**
    *
    * @return void
    */
    public function place_to_fields()
    {
        $this->name->setValue($this->get_place()->get_name());
        $this->address->setValue($this->get_place()->getAddress()->address);
        $this->address2->setValue($this->get_place()->getAddress()->address2);
        $this->city->setValue($this->get_place()->getCity()->get_value());
        $this->state->setValue($this->get_place()->getState()->get_value());
        $this->postalcode->setValue($this->get_place()->getPostal()->get_value());

    }
    
}

Zupal_Places is a domain object -- but it could just as easily be a Zend_Db_Table_Rowset object, if you mentally replace $this->get_place()->getCity() with $this->get_place()->city etc.

Note also two things:

  1. The form objects are referred to as "virtual properties" as opposed to by using getElement('ele_name').
  2. The selector for country is populated by an array of ID-keyed country names. Zend_Form_Element_MultiOption elements are set using setMultiOptions and addMultiOptions methods. (even a dropdown select is considered a "multiOption" element for this purpose.)

A Final Thought

There are many ways in which Zend Forms blow.

They have a tremendous learning curve.

If you want what is truly resource efficient, you should be using a client side javascript library that produces a Dojo form.

They freeze in the abstract that which should truly be fairly concrete. While the definition of a form can be abstracted into a configuration, each use of the form must pass through a dizzying tree of objects to render itself. There is no way to "Bake" the form into a specific low-impact object without losing the ability to re-seed it with new data. And yes, I will soon produce an example to the contrary.

Most fatally: Forms place an element that designers are frequently called upon to augment squarely within the control of the engineering department. This has the potential to create inter-team friction.

Designer:
I need to change the label and form type of field Foo of form Bar.
Engineer:
Just edit the config file.
Designer:
I don't know how.
Engineer:
Read the specs on the Zend Website.
Designer:
I never learned to read!
Engineer:
Too bad. I am vastly more important than you and your womanly tears do not touch my icy machinelike heart.
Designer:
You're mean. I'm telling my Art Director on you!
Engineer:
Suck it bitch! King Kong ain't got nothing on me!

Truth is, Designers have long regarded forms as THEIR territory, as they are at root a visual element, and they are justified in doing so. Zend Forms change that dynamic, in a manner least likely to make either party happy. Engineers hate getting tasked with maintining visual elements, because they are often changed to meet the need of overly sensitive product managers, art directors, and other folk that somehow manage to find work despite their lack of technical expertise(virility). 

Lastly, what do you get in exchange? The ability to seed a form with data and validate it when it comes back at you. Neither of these tasks were ever a serious roadblock to developers in the first place. Yes, they also add a measure of consistency to the process, but there's already a device in place to solve that problem: the style sheet.

There is a third payoff that forms give you, of course, as a professional engineer: once you encrypt forms in the Zend_Form library, the rest of the company will be totally F**ked if you leave. So if that is your option, read, read and read this document again and proceed to insert your newfound skills deep inside the dark parts of your clients codebase. They may struggle at first but trust me, they want it as bad as you do.

A Better World, Free From Sin

 

Zupal FastForms is my attempt to generate forms in a mutable form (i.e., with templates that generate their own render code in PHP that is independant of the code library and eminently editable by the art department. it is VERY alpha but I did it in a lazy weekend and I already think it has if not the full feature set of Zend_Forms, it does at least have the core functionality and some new features to boot.

What it lacks is validation, which is very viable as an independant add-on inside recipient actions anyway.

Reply

  • Lines and paragraphs break automatically.
  • Allowed HTML tags: <a> <p> <span><small> <div> <h1> <h2> <h3> <h4> <h5> <h6> <img> <map> <area> <hr> <br> <br /> <ul> <ol> <li> <dl> <dt> <dd> <table> <tr> <td> <em> <b> <u> <i> <strong> <font> <del> <ins> <sub> <sup> <quote> <blockquote> <pre> <address> <code> <cite> <embed> <object> <param> <strike> <caption>
  • Web page addresses and e-mail addresses turn into links automatically.

More information about formatting options