Wednesday, October 21, 2009

Advanced Zend_Form usage

Zend_Form is the component of Zend Framework that I enjoy the most: it implements reusable forms and inputs as classes and objects, with automatic reinsertion of values during server-side validation; validation that is shared among all the instances and is provided out of the box by Zend_Validate. If you have ever duplicated a form for editing and adding a new entity to your application, or have felt the pain to manually populate text inputs, you now know being able to reuse a form is a killer feature and in fact many php frameworks provide a form library.
Today I have gathered from my experience some know-how I have learnt while taking the Zend_Form component to its limits.

Ignored elements
Every Zend_Form_Element instance has a method setIgnore(). Also you can pass a flag 'ignore' equal to True in the $options array parameter in the constructor, respecting the framework convention, to obtain the same result.
The purpose of this option is to exclude the value assumed by this input from the result returned by getValues(), even if you pass to isValid() the entire POST request (which contains a value for this specific element since it was present on the client side and filled for other purposes). This option comes handy when using Zend_Form_Element_Captcha or Zend_Form_Element_Submit.
$button = new Zend_Form_Element_Submit('submitButton', array('ignore' => true, 'label' => 'Send!'));

Decorators
Both elements and forms (descendants of Zend_Form_Element and Zend_Form respectively) support a stack of decorators used for rendering themselves. The first decorator, ViewHelper, calls a view helper defined by the element or form to produce the basic html and this string result is passed to the subsequent decorators in a chain, each of them working on the previous one output.
This design is an unconventional implementation of the Decorator Pattern, and allow decorators to be reused troughout every kind of form element. The standard decorators cover a vast variety of cases and allow you to produce custom html for your forms. You can even write your own ones by extending Zend_Form_Decorator_Abstract.
$element = new Zend_Form_Element_Text();
$element->addDecorators(array(
    'ViewHelper',
    'Errors',
    array('Description', array('tag' => 'div', 'class' => 'description')),
    array('HtmlTag', array('tag' => 'dd')),
    array('Label', array('tag' => 'dt'))
));

Subforms
Subforms are instances of Zend_Form which are incorporated in a parent form, providing reusability of logic groups of elements. Generally I prefer to use Zend_Form instances with the adequate decorators instead of Zend_Form_SubForm ones, which is also a subclass of it.
Not only you can reutilize forms as fieldsets of a bigger one, but you can encanpsulate the values of this subform's elements transforming the main result in a recursive array.
        $form->setDecorators(array(
                    'FormElements',
                    array('HtmlTag', array('tag' => 'dl')),
                    'FieldSet');
$form->setElementsBelongTo('my_key'); // call this after all elements have already been inserted
// ...
if ($form->isValid($postData)) {
    $values = $form->getValues();
    // $values['my_key'] is an array containing the values of subform's elements
}

Dojo
The Zend_Dojo component contains form elements which extends or substitutes the standard Zend_Form ones, augmenting their capabilities with the Dojo Toolkit widgets.
To set up the right plugin path, simply make your form a subclass of Zend_Dojo_Form instead of Zend_Form, or pass the form instance to the Zend_Dojo::enableForm() static method. Also make sure you are outputting the $this->dojo() view helper in your layout or view script, to print the mandatory script tags.
For instance you can create a real-time filtered select:
$element = $form->createElement('FilteringSelect', 'nameOfelement');
$element->setMultiOptions(array('yellow' => 'Light Yellow', 'blue'   => 'Blue', ...);
This type of elements can also work with a remote data store, just in case you have one million select options and you want to lazy load them. There is even a configurable WYSIWYG editor in the dojo component, available via CDN without having to upload a single js file to your server.

Captchas
With Zend_Form_Element_Captcha, you can create different types of captcha to insert in your forms for the sake of stopping spammers bots to submit them. A captcha not correctly answered will invalidate the form result during the call to Zend_form::isValid(). Though you can specify ascii art as captchas and even on-the-fly generated images, this is the fastest way to include a captcha in a form:
$element = new Zend_Form_Element_Captcha();
$element->setCaptcha('Dumb')
              ->setIgnore(true);
You may be worried about how to automatically test the submit of a form which contains a captcha, since it is built with the purpose of avoid being answered by a machine like your test runner. However, there is a trick that accesses session variables to find the right answer for the captcha during the test method run.

I hope this quick panoramic of functionalities satisfies you. Zend_Form is a complex component but the learning curve is really worth the benefits it gives to your applications. You can even use it in isolation, without the ZF Mvc stack, since the only requirement is a Zend_View object that every element and form instance needs to render itself.
With Zend_Dojo_Form not even uploading javascript plugins is needed for improving the user experience: dojo is loaded remotely via Google or Aol servers. Zend_Form is a complete open source, liberally licensed, object-oriented solution for managing forms and inputs, the most powerful choice I have ever seen in php and one of the top components of Zend Framework.

7 comments:

Sudheer said...

Zend_Form is indeed a powerful component. The decorators have had me puzzled for a while. Matthew and others have blogged extensively about using decorators to get the desired markup output.

setIgnore() is cool. Thanks for mentioning.

"This design is an unconventional implementation of the Decorator Pattern"
What exactly do you mean by that? Care to explain?

I have been using subforms only the sake of array notation.

The biggest limitation of Zend_Form is currently it doesn't support nested array element rendering out of the box. Something like input name="customer[type][location][range]". Correct me if I'm wrong.


Dojo and CAPTCHA elements are so useful. Few months ago, I wrote about using Zend_Dojo_Form on my blog.

Giorgio said...

Sudheer,
the classic Decorator pattern implies an interface to conform to. For instance if there was a Zend_Form_Interface and I designed a Zend_Form_Decorator_FieldSet which implements it along with Zend_Form, I would have done the conventional implementation. This decorator would take a Zend_Form instance in the constructor and forward every method call to it, apart the render() call which is embellished. This technique allow multiple decorators, where everyone wraps the previous one, but it is not appropriate as Zend_Form rendering is a secondary responsibility.
I am not sure about nested arrays, as I used this feature only with subforms.

Wojciech Szela said...

What I find very useful are custom elements (with custom decorators), subforms and validators.

You can have few different forms, eg. user's login data, user's address and user's privacy settings and use them separately or combine into bigger form. Pretty handy when you want to display same or similar form in different places or depending on access level or any other condition.

Custom elements make easy handling complex data types. For instance a date can be divided into year, month and day each one requiring separate text input/select, but being validated together. Creating custom element composed of three such inputs and data atoms and handling such data as one makes things much simpler. And date example is simple one.

There is also not well know feature of validators, that is context. A validator can depend on context, that is on values of other inputs. How can it help? Well, password confirmation, valid post code depending on selected province, dependent birth date and national identification number (at least in Poland)...

Zend_Form is very powerfull, but as Sudheer said puzzling. Once mastered saves your time.

Giorgio said...

Wojciech,
I agree that custom elements are a valid feature. My other favourite example is the "repeated password" element, which displays two masked inputs and validates that they are the same during a registration process.
As for the context, I try to avoid it in custom elements as they become more reusable (unless you specify context keys via some configuration).

Anonymous said...

I didn't know setIgnore, nice.

Ah, these decorators, I really think they should have been implemented differently. They just are not user friendly. That's why you need to read countless blog articles about them to barely get it.

In some way, I could punch in the face the guy who made them if he was in front of me. Ok, just kidding :) (a bit ...)

Wojciech Szela said...

Giorgio: context keys are specified by config, no hardcoding :) "repeated password" is something I validate this way.

Since we already have decent list of not well known, cool features what do you miss most? I vote for multipage (multirequest) forms support out of the box.

Giorgio said...

I think multiple page forms are rare... I would be glad if there was a one-time generation feature for small projects that work with ActiveRecord.

ShareThis