Form Quick Start

Forms are relatively easy to create. At the bare minimum, each element or fieldset requires a name; typically, you’ll also provide some attributes to hint to the view layer how it might render the item. The form itself will also typically compose an InputFilter– which you can also conveniently create directly in the form via a factory. Individual elements can hint as to what defaults to use when generating a related input for the input filter.

Form validation is as easy as providing an array of data to the setData() method. If you want to simplify your work even more, you can bind an object to the form; on successful validation, it will be populated from the validated values.

Programmatic Form Creation

If nothing else, you can simply start creating elements, fieldsets, and forms and wiring them together.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
use Zend\Captcha;
use Zend\Form\Element;
use Zend\Form\Fieldset;
use Zend\Form\Form;
use Zend\InputFilter\Input;
use Zend\InputFilter\InputFilter;

$name = new Element('name');
$name->setLabel('Your name');
$name->setAttributes(array(
    'type'  => 'text'
));

$email = new Element\Email('email');
$email->setLabel('Your email address');

$subject = new Element('subject');
$subject->setLabel('Subject');
$subject->setAttributes(array(
    'type'  => 'text'
));

$message = new Element\Textarea('message');
$message->setLabel('Message');

$captcha = new Element\Captcha('captcha');
$captcha->setCaptcha(new Captcha\Dumb());
$captcha->setLabel('Please verify you are human');

$csrf = new Element\Csrf('security');

$send = new Element('send');
$send->setValue('Submit');
$send->setAttributes(array(
    'type'  => 'submit'
));


$form = new Form('contact');
$form->add($name);
$form->add($email);
$form->add($subject);
$form->add($message);
$form->add($captcha);
$form->add($csrf);
$form->add($send);

$nameInput = new Input('name');
// configure input... and all others
$inputFilter = new InputFilter();
// attach all inputs

$form->setInputFilter($inputFilter);

As a demonstration of fieldsets, let’s alter the above slightly. We’ll create two fieldsets, one for the sender information, and another for the message details.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$sender = new Fieldset('sender');
$sender->add($name);
$sender->add($email);

$details = new Fieldset('details');
$details->add($subject);
$details->add($message);

$form = new Form('contact');
$form->add($sender);
$form->add($details);
$form->add($captcha);
$form->add($csrf);
$form->add($send);

Regardless of approach, as you can see, this can be tedious.

Creation via Factory

You can create the entire form, and input filter, using the Factory. This is particularly nice if you want to store your forms as pure configuration; you can simply pass the configuration to the factory and be done.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
use Zend\Form\Factory;

$factory = new Factory();
$form    = $factory->createForm(array(
    'hydrator' => 'Zend\Stdlib\Hydrator\ArraySerializable',
    'elements' => array(
        array(
            'spec' => array(
                'name' => 'name',
                'options' => array(
                    'label' => 'Your name',
                ),
                'type'  => 'Text',
            )
        ),
        array(
            'spec' => array(
                'type' => 'Zend\Form\Element\Email',
                'name' => 'email',
                'options' => array(
                    'label' => 'Your email address',
                )
            ),
        ),
        array(
            'spec' => array(
                'name' => 'subject',
                'options' => array(
                    'label' => 'Subject',
                ),
                'type'  => 'Text',
            ),
        ),
        array(
            'spec' => array(
                'type' => 'Zend\Form\Element\Textarea',
                'name' => 'message',
                'options' => array(
                    'label' => 'Message',
                )
            ),
        ),
        array(
            'spec' => array(
                'type' => 'Zend\Form\Element\Captcha',
                'name' => 'captcha',
                'options' => array(
                    'label' => 'Please verify you are human.',
                    'captcha' => array(
                        'class' => 'Dumb',
                    ),
                ),
            ),
        ),
        array(
            'spec' => array(
                'type' => 'Zend\Form\Element\Csrf',
                'name' => 'security',
            ),
        ),
        array(
            'spec' => array(
                'name' => 'send',
                'type'  => 'Submit',
                'attributes' => array(
                    'value' => 'Submit',
                ),
            ),
        ),
    ),
    /* If we had fieldsets, they'd go here; fieldsets contain
     * "elements" and "fieldsets" keys, and potentially a "type"
     * key indicating the specific FieldsetInterface
     * implementation to use.
    'fieldsets' => array(
    ),
     */

    // Configuration to pass on to
    // Zend\InputFilter\Factory::createInputFilter()
    'input_filter' => array(
        /* ... */
    ),
));

If we wanted to use fieldsets, as we demonstrated in the previous example, we could do the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
use Zend\Form\Factory;

$factory = new Factory();
$form    = $factory->createForm(array(
    'hydrator'  => 'Zend\Stdlib\Hydrator\ArraySerializable',
    'fieldsets' => array(
        array(
            'spec' => array(
                'name' => 'sender',
                'elements' => array(
                    array(
                        'spec' => array(
                            'name' => 'name',
                            'options' => array(
                                'label' => 'Your name',
                            ),
                            'type' => 'Text'
                        ),
                    ),
                    array(
                        'spec' => array(
                            'type' => 'Zend\Form\Element\Email',
                            'name' => 'email',
                            'options' => array(
                                'label' => 'Your email address',
                            ),
                        ),
                    ),
                ),
            ),
        ),
        array(
            'spec' => array(
                'name' => 'details',
                'elements' => array(
                    array(
                        'spec' => array(
                            'name' => 'subject',
                            'options' => array(
                                'label' => 'Subject',
                            ),
                            'type' => 'Text',
                        ),
                    ),
                    array(
                        'spec' => array(
                            'name' => 'message',
                            'type' => 'Zend\Form\Element\Textarea',
                            'options' => array(
                                'label' => 'Message',
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ),
    'elements' => array(
        array(
            'spec' => array(
                'type' => 'Zend\Form\Element\Captcha',
                'name' => 'captcha',
                'options' => array(
                    'label' => 'Please verify you are human. ',
                    'captcha' => array(
                        'class' => 'Dumb',
                    ),
                ),
            ),
        ),
        array(
            'spec' => array(
            'type' => 'Zend\Form\Element\Csrf',
            'name' => 'security',
        ),
    ),
    array(
        'spec' => array(
            'name' => 'send',
            'type'  => 'Submit',
            'attributes' => array(
                'value' => 'Submit',
            ),
        ),
     ),
    ),
    // Configuration to pass on to
    // Zend\InputFilter\Factory::createInputFilter()
    'input_filter' => array(
    /* ... */
    ),
));

Note that the chief difference is nesting; otherwise, the information is basically the same.

The chief benefits to using the Factory are allowing you to store definitions in configuration, and usage of significant whitespace.

Factory-backed Form Extension

The default Form implementation is backed by the Factory. This allows you to extend it, and define your form internally. This has the benefit of allowing a mixture of programmatic and factory-backed creation, as well as defining a form for re-use in your application.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
namespace Contact;

use Zend\Captcha\AdapterInterface as CaptchaAdapter;
use Zend\Form\Element;
use Zend\Form\Form;

class ContactForm extends Form
{
    protected $captcha;

    public function __construct(CaptchaAdapter $captcha)
    {
        $this->captcha = $captcha;

        // add() can take either an Element/Fieldset instance,
        // or a specification, from which the appropriate object
        // will be built.

        $this->add(array(
            'name' => 'name',
            'options' => array(
                'label' => 'Your name',
            ),
            'type'  => 'Text',
        ));
        $this->add(array(
            'type' => 'Zend\Form\Element\Email',
            'name' => 'email',
            'options' => array(
                'label' => 'Your email address',
            ),
        ));
        $this->add(array(
            'name' => 'subject',
            'options' => array(
                'label' => 'Subject',
            ),
            'type'  => 'Text',
        ));
        $this->add(array(
            'type' => 'Zend\Form\Element\Textarea',
            'name' => 'message',
            'options' => array(
                'label' => 'Message',
            ),
        ));
        $this->add(array(
            'type' => 'Zend\Form\Element\Captcha',
            'name' => 'captcha',
            'options' => array(
                'label' => 'Please verify you are human.',
                'captcha' => $this->captcha,
            ),
        ));
        $this->add(new Element\Csrf('security'));
        $this->add(array(
            'name' => 'send',
            'type'  => 'Submit',
            'attributes' => array(
                'value' => 'Submit',
            ),
        ));

        // We could also define the input filter here, or
        // lazy-create it in the getInputFilter() method.
    }
}

You’ll note that this example, the elements are added in the constructor. This is done to allow altering and/or configuring either the form or input filter factory instances, which could then have bearing on how elements, inputs, etc. are created. In this case, it also allows injection of the CAPTCHA adapter, allowing us to configure it elsewhere in our application and inject it into the form.

Validating Forms

Validating forms requires three steps. First, the form must have an input filter attached. Second, you must inject the data to validate into the form. Third, you validate the form. If invalid, you can retrieve the error messages, if any.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$form = new Contact\ContactForm();

// If the form doesn't define an input filter by default, inject one.
$form->setInputFilter(new Contact\ContactFilter());

// Get the data. In an MVC application, you might try:
$data = $request->getPost();  // for POST data
$data = $request->getQuery(); // for GET (or query string) data

$form->setData($data);

// Validate the form
if ($form->isValid()) {
    $validatedData = $form->getData();
} else {
    $messages = $form->getMessages();
}

You can get the raw data if you want, by accessing the composed input filter.

1
2
3
4
$filter = $form->getInputFilter();

$rawValues    = $filter->getRawValues();
$nameRawValue = $filter->getRawValue('name');

Hinting to the Input Filter

Often, you’ll create elements that you expect to behave in the same way on each usage, and for which you’ll want specific filters or validation as well. Since the input filter is a separate object, how can you achieve these latter points?

Because the default form implementation composes a factory, and the default factory composes an input filter factory, you can have your elements and/or fieldsets hint to the input filter. If no input or input filter is provided in the input filter for that element, these hints will be retrieved and used to create them.

To do so, one of the following must occur. For elements, they must implement Zend\InputFilter\InputProviderInterface, which defines a getInputSpecification() method; for fieldsets, they must implement Zend\InputFilter\InputFilterProviderInterface, which defines a getInputFilterSpecification() method.

In the case of an element, the getInputSpecification() method should return data to be used by the input filter factory to create an input. Every HTML5 (email, url, color…) elements have a built-in element that use this logic. For instance, here is how the Zend\Form\Element\Color element is defined:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
 namespace Zend\Form\Element;

 use Zend\Form\Element;
 use Zend\InputFilter\InputProviderInterface;
 use Zend\Validator\Regex as RegexValidator;
 use Zend\Validator\ValidatorInterface;

 class Color extends Element implements InputProviderInterface
 {
     /**
      * Seed attributes
      *
      * @var array
      */
     protected $attributes = array(
         'type' => 'color',
     );

     /**
      * @var ValidatorInterface
      */
     protected $validator;

     /**
      * Get validator
      *
      * @return ValidatorInterface
      */
     protected function getValidator()
     {
         if (null === $this->validator) {
             $this->validator = new RegexValidator('/^#[0-9a-fA-F]{6}$/');
         }
         return $this->validator;
     }

     /**
      * Provide default input rules for this element
      *
      * Attaches an email validator.
      *
      * @return array
      */
     public function getInputSpecification()
     {
         return array(
             'name' => $this->getName(),
             'required' => true,
             'filters' => array(
                 array('name' => 'Zend\Filter\StringTrim'),
                 array('name' => 'Zend\Filter\StringToLower'),
             ),
             'validators' => array(
                 $this->getValidator(),
             ),
         );
     }
 }

The above would hint to the input filter to create and attach an input named after the element, marking it as required, and giving it a StringTrim and StringToLower filters and a Regex validator. Note that you can either rely on the input filter to create filters and validators, or directly instantiate them.

For fieldsets, you do very similarly; the difference is that getInputFilterSpecification() must return configuration for an input filter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
namespace Contact\Form;

use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Validator;

class SenderFieldset extends Fieldset implements InputFilterProviderInterface
{
    public function getInputFilterSpecification()
    {
        return array(
            'name' => array(
                'required' => true,
                'filters'  => array(
                    array('name' => 'Zend\Filter\StringTrim'),
                ),
            ),
            'email' => array(
                'required' => true,
                'filters'  => array(
                    array('name' => 'Zend\Filter\StringTrim'),
                ),
                'validators' => array(
                    new Validator\EmailAddress(),
                ),
            ),
        );
    }
}

Specifications are a great way to make forms, fieldsets, and elements re-usable trivially in your applications. In fact, the Captcha and Csrf elements define specifications in order to ensure they can work without additional user configuration!

Note

If you set custom input filter specification either in getInputSpecification() or in getInputFilterSpecification(), the Zend\InputFilter\InputInterface set for that specific field is reset to the default Zend\InputFilter\Input.

Some form elements may need a particular input filter, like Zend\Form\Element\File: in this case it’s mandatory to specify the type key in your custom specification to match the original one (in ex. for the file element it’s Zend\InputFilter\FileInput).

Binding an object

As noted in the intro, forms in Zend Framework bridge the domain model and the view layer. Let’s see that in action.

When you bind() an object to the form, the following happens:

  • The composed Hydrator calls extract() on the object, and uses the values returned, if any, to populate the value attributes of all elements. If a form contains a fieldset that itself contains another fieldset, the form will recursively extract the values.
  • When isValid() is called, if setData() has not been previously set, the form uses the composed Hydrator to extract values from the object, and uses those during validation.
  • If isValid() is successful (and the bindOnValidate flag is enabled, which is true by default), then the Hydrator will be passed the validated values to use to hydrate the bound object. (If you do not want this behavior, call setBindOnValidate(FormInterface::BIND_MANUAL)).
  • If the object implements Zend\InputFilter\InputFilterAwareInterface, the input filter it composes will be used instead