Reply to comment
Zend Forms: my, how they blow!
Submitted by bingomanatee on 13 April, 2009 - 23:19Ultimately 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 markupWhen you are writing a responder to recieve data from a Zend_Form you can use the isValid(); method to do two things at once:
- Populate your form elements with values from a name-keyed hash
- 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 a boolean function - however it does two things at the same time: it POPULATES your fields with the passed values and it EVALUATES these values against field criteria. (okay it does THREE things. invalid values trigger an error message to display when the form renders.)
If you have no criteria at all on your form isValid is still the best (only) way to pre-poulate your form en masse from a key value hash.
$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
- Create field elements, and their options
- Make Field Elements
- Set their validation criteria and labels
- Add them to the form*
- render the form
- retrieve the data from a form submission
- Validate the data
- 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> </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:
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 form such as jQuery or Dojo.
- They freeze in the abstract that which should truly be fairly concrete. By which I mean: in Zend forms are a torrent of objects and methods, but what they produce is in most ways, an invariant display token. While you can put caching around a zend form, it seems to me that the forms should have an internal caching mechanism for increased efficiency. 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. 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.
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
I am working on a Zend Form killer; I had a first pass, but I don't like it enough to put it out. However I would point out that ZF's validation library is available outside of the Form component and Dojo has a great form library that doesn't require any server side analog. Between the two you have 90% of what you get with ZF forms -- or 110% if client side validation is a win from your point of view. If you are Flash friendly, flash has an amazing set of tools and form components, and they of course have a deep programming language for validation.
If you are still enamored with the ZF forms approach, keep in mind you do not HAVE to use $form->render() to render a form - you can lay out the form with stock HTML and render individual elements inside your static layout, parametrically accessing labels, error messages, descriptions, et. all. This gives a lot of control back to your designers.
And just to be clear - ZF is not the first group that has tried to force forms into a framework and they are definately not the worst example of this sort of task. Drupal and Symfony also have elements in this area and I find the Drupal approach much more diffuse and distracting than ZF. I just find ZF's all in one approach extremely dense and disarming, and a module that requires a lot of self education for what is a fairly straightforward task before you commit to enterprise level form management.
*Note - step 1...4 can be done in one fell swoop with a configuration file, or procedurally with a series of OOP steps. Once you are good at Zend Forms (which you shouldn't get, good at because they suck) you'll find config files a great timesaver, but its good to know both methods of element manipulation.
