Zend Framework 2 Forms – Simple and Available Everywhere

Zend Framework 2 Forms

I think it goes without saying, forms are one of the central elements of any web-based application. They’re used for everything from logging in, to searching content and managing information. Given that, they should be first-class citizens, able to be developed and reused with relative ease.

I know for myself, on the web application projects which Malt Blue is currently undertaking, I need to be able to develop them as quickly and flexibly as possible, to help ensure project budgets and timelines are not overrun.

However, given the amount of options, configurability and flexibility required, this isn’t always easy. With Zend Framework 2, I believe the contributors have made some very worthy improvements, which help forms become the first class citizens they need to be.

In today’s post, I’m going to assume you have a basic understanding of how forms work now. So if I skip over any aspect of theory you’re not familiar with, there will be a number of links in the further reading section to help you.

I’m going to show you how to create flexible, reusable forms in one module and by the power of the ServiceManager reuse them throughout your application. Sound fun? Great. Let’s get started.

Setting Up Dependencies

One thing I need to mention is, though quite subtly, the form that we’re creating will be extending the ProvidesEventsForm class provided in ZfcBase. The simplest way to get it is to make it available is via ZfcUser in Composer. So, in your project’s composer.json file, add the following:

"require": {
    ...
    "zf-commons/zfc-user": "dev-master",
    ...
}

Then run the following to bring in the module to your vendor directory:

composer update

Under the vendor directory, you will have a new directory, zf-commons and under it, two directories: zfc-base and zfc-user. Enable ZfcBase in your application by adding ‘ZfcBase’ to the modules section of your config/application.config.php file. We now have all of the dependencies we need.

Creating the Reusable Module

The first thing we need to do is to create our new module. For simple nomenclature, let’s call it ContentCore. Whether by hand or with the assistance of ZF Tool, create a new module with the following structure:

  • ContentCore
    • config
      • module.config.php
    • src
      • ContentCore
        • Entity
          • User.php
        • Form
          • UserFieldset.php
          • CreateUser.php
    • Module.php

This structure provides the basic requirements of a module, which are the Module.php file and the config/module.config.php file. Then we have the User class under Entity and two further files under Form.

I’ve skipped along a bit here; but what we’re going to do is to do minimal work in each section. The form will provide the form infrastructure, along with a submit button and CSRF security field.

The UserFieldset will handle rendering the Entity elements, stipulating the field types, options and validation rules. Finally, the Entity class will store the information passed through to the form by the user. Let’s now get in to the nitty gritty of each one.

The Entity Class

The entity class allows us to model and manipulate information the object requires. As you can see below, it has three properties:

  • UserId
  • firstName
  • lastName

It’s fair to say this object can be mapped to a record in a database, but it needn’t be. It has two methods:

  • exchangeArray
  • getFullName

exchangeArray allows data from an external source to be mapped to the internal properties of the object. getFullName returns the full name of the user, like a contact list would, i.e., first name then last name or vice versa.

namespace ContentCore\Entity;

class User
{
    CONST DISPLAY_NAME_FIRSTLAST = "firstLast";
    CONST DISPLAY_NAME_LASTFIRST = "lastFirst";

    public $UserId;

    public $firstName;

    public $lastName;

    public function exchangeArray($data)
    {
        $this->UserId = (isset($data['UserId'])) ? $data['UserId'] : null;
        $this->firstName = (isset($data['FirstName'])) ? $data['FirstName'] : null;
        $this->lastName = (isset($data['LastName'])) ? $data['LastName'] : null;
    }

    public function getFullName($displayOrder = self::DISPLAY_NAME_FIRSTLAST)
    {
        if ($displayOrder === self::DISPLAY_NAME_FIRSTLAST) {
            return sprintf("%s %s", $this->firstName, $this->lastName);
        } else {
            return sprintf("%s %s", $this->lastName, $this->firstName);
        }
    }
}

Overall, nothing too difficult here.

The Fieldset Class

In the UserFieldset class, it gets a bit more difficult. Fieldsets are a way of grouping logically related elements and being able to dynamically share them between different forms.

The setObject method is used so the fieldset can extract from and set information supplied in the User object we just talked about. The setHydrator method, receiving the ObjectPropertyHydrator object is used to work with the User object.

If you’re not familiar with Hydrators, the Zend Framework 2 manual describes hydration as:

Hydration is the act of populating an object from a set of data.

There are a number of hydrators available. The ObjectPropertyHydrator is able to look at the properties of the entity provided, to know which values to populate in the form. I used this one to avoid writing specific methods.

Consequently, the entity properties and the form field element names need to match up. Your approach may differ. I’ll work through the class a bit more now.

use ContentCore\Entity\User;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Stdlib\Hydrator\ObjectProperty as ObjectPropertyHydrator;

class UserFieldset extends Fieldset 
    implements InputFilterProviderInterface
{
    public function __construct()
    {
        parent::__construct('User');
        $this->setHydrator(new ObjectPropertyHydrator(false))
             ->setObject(new User());

This sets the label in the fieldset element.

        $this->setLabel('User');

Here we add two elements to the fieldset, specifying the name, options and attributes. As we’ve not set a specific type, the element will be rendered as a standard text field. If you want, specify any other in-built or custom one you like. Have a look under Zend\Form\Element for the in-built options available.

        $this->add(array(
            'name' => 'firstName',
            'options' => array(
                'label' => 'User\'s First Name'
            ),
            'attributes' => array(
                'required' => 'required'
            )
        ));

        $this->add(array(
            'name' => 'lastName',
            'options' => array(
                'label' => 'User\'s Last Name'
            ),
            'attributes' => array(
                'required' => 'required'
            )
        ));
    }

As the class implements InputFilterProviderInterface, it needs to implement the getInputFilterSpecification method. This specifies the validation rules for the fieldset.

I’ve just kept these relatively simple. However, you could get quite complex if you like. You can see all I’ve specified are that both the firstName and lastName fields are required. What other options would you consider specifying?

    /**
     * @return array
     */
    public function getInputFilterSpecification()
    {
        return array(
            'firstName' => array(
                'required' => true,
            ),
            'lastName' => array(
                'required' => true,
            )
        );
    }
}

The User Form

Now unless it’s not clear so far, it should become so rather quickly. The reason why I’ve decided to use Fieldsets is because with them, I can group together logically related form elements, along with related requirements for them, then pass them in to any form I create later.

In building Zend Framework 1 applications, I spent a lot of time with XML-based configurations and field groups with the intent of doing this. But to be honest, it was all rather a lot of overhead and work. Forms in Zend Framework 2 make this tremendously simpler.

use Zend\Form\Form;
use Zend\Form\Element;
use ZfcBase\Form\ProvidesEventsForm;
use Zend\InputFilter\InputFilter;
use Zend\Stdlib\Hydrator\ObjectProperty as ObjectPropertyHydrator;

Though I’ve not done anything with it, my CreateUser form extends ProvidesEventsForm from the ZfcBase module by Evan Coury.

This sets up a form so it’s EventManager aware. Other objects can now register to respond to events this form can trigger during its lifecycle. Perhaps you might want to send a tweet or email after information has been saved or updated?

class CreateUser extends ProvidesEventsForm
{
    public function __construct()
    {
        parent::__construct('User');

        $this->setAttribute('method', 'post')
             ->setHydrator(new ObjectPropertyHydrator(false))
             ->setInputFilter(new InputFilter());

We make the fieldset available to the form, which will render the elements it contains

        $this->add(array(
            'type' => 'ContentCore\Form\UserFieldset',
            'options' => array(
                'use_as_base_fieldset' => true
            )
        ));

As a base form contains no elements by itself, we now add two: a Csrf field and submit button. The Csrf field will contain an auto generated token to ensure the form submission was not spoofed.

        $this->add(array(
            'type' => 'Zend\Form\Element\Csrf',
            'name' => 'csrf'
        ));

        $this->add(array(
            'name' => 'submit',
            'attributes' => array(
                'type' => 'submit',
                'value' => 'Send'
            )
        ));
    }
}

With that, our form is complete. Now let’s take it a step further and make the form available anywhere in our application.

The Module Configuration

Under ContentCore\Module.php, first add the following use statements, so all of the definitions are available.

use ContentCore\Entity\User;
use ContentCore\Model\UserTable;
use Zend\Db\ResultSet\ResultSet;
use Zend\Db\TableGateway\TableGateway;

Then, in getServiceConfig, add the following definition. What this will do is to make available a key in the ServiceManager, which references an instantiated copy of the form we’ve just setup.

public function getServiceConfig()
{
    // ...
    return array(
    'contentuser_create_user_form' => function($sm) {
        $form = new Form\CreateUser();
        return $form;
    },
    // …
}
NB: It was pointed out on #zftalk on IRC, that getServiceConfig wasn’t the recommended place to configure forms. For a better way, have a look at Howto Handle External Form Element Dependencies with FormElementManager

The Application Configuration

Now all of this work is nothing, if we don’t enable the module in the application. So in config/application.config.php, add the following under ‘modules’ at the top of the file.

'ContentCore',

The Application Controller Action

Right, so now we have our form setup, it’s available throughout the application via the ServiceManager and the module’s enabled in our application. Let’s get about using it in our controller action.

I created a trivial function formtestAction, in the IndexController class which comes with the default Application module to accommodate this.

You can see below I have three functions and a protected member variable. The member variable will contain the form, just to make things a bit simpler. The _getCreateUserForm function checks if the variable is defined.

If not, it retrieves a copy of the form via the ServiceLocator using the string we defined earlier and calls setCreateUserForm. Otherwise, it returns the form in createUserForm.

protected $_createUserForm;

protected function _getCreateUserForm()
{
    if (!$this->_createUserForm) {
        $this->_setCreateUserForm(
            $this->getServiceLocator()->get('coalescentuser_create_user_form')
        );
    }
    return $this->_createUserForm;
}

protected function _setCreateUserForm(\Zend\Form\Form $createUserForm)
{
    $this->_createUserForm = $createUserForm;
}

The last function, formtestAction, calls _getCreateUserForm and binds a new User entity to it. As mentioned earlier, this takes care of retrieving information from and persisting information in the User entity.

We then check if the request was a post, set form data from the post request information and run the form validation routine. There’s nothing special here and I’ve deliberately not fleshed out the steps after a successful validation.

The action finishes up by returning a ViewModel with one property, our form. This, in my mind, is a lot simpler than the approach Zend Framework 1 Forms took. It requires less code and is simpler to understand. In short, irrespective of the state of the form, one line makes it available to our view template.

public function formtestAction()
{
    $form = $this->_getCreateUserForm();
    $User = new User();
    $form->bind($User);

    if ($this->request->isPost()) {
        $form->setData($this->request->getPost());

        if ($form->isValid()) {
            // take action
        }
    }

    return new ViewModel(array('form' => $form));
}

The View Template

Ok, last piece in the puzzle – the view template. Please make a special note of the prepare method call. If you miss this, you’ll be scratching your head wondering why things don’t work. What this does is to:

ensure validation error messages are available, and prepares any elements and/or fieldsets that require preparation.

<?php $form->prepare(); ?>

Next, we use the form helper to open the form, retrieving properties from the form object.

<?php echo $this->form()->openTag($form); ?>

We then get access to the user fieldset, so we can use the formRow helper to render the elements for the firstName and lastName.

<?php $User = $form->get('User'); ?>

<?php echo $this->formRow($User->get('firstName')); ?>
<?php echo $this->formRow($User->get('lastName')); ?>

We then finish up by rendering the csrf and submit elements and close the form.

<?php echo $this->formHidden($form->get('csrf')); ?>
<?php echo $this->formInput($form->get('submit')); ?>

<?php echo $this->form()->closeTag($form); ?>

In Conclusion

We’ve now stepped through the process of creating a form, based on an entity which’s reusable anywhere in our application. I hope you can see forms truly are first class citizens which, combined with the Service Manager, have a tremendous amount of flexibility, configurability and reusability.

I appreciate it was rather a rapid discussion of the building blocks of the process. So if you need any further information about what was covered here – let me know in the comments.

Also, tell me in the comments how you’re using forms in your Zend Framework 2 applications? What novel and innovative ways are you approaching them?

Further Reading

featured image, credit Brian J Bruemmer

Intermediate Tutorial Zend Framework
  • http://www.automateandvalidate.com/ Bikram ku das

    However, given the amount of options, configurability and flexibility required, this isn’t always easy. With Zend Framework 2, I believe the contributors have made some very worthy improvements, which help forms become the first class citizens they need to be.

    • http://www.matthewsetter.com/ maltblue

      @Bikram ku das Bikram, I do agree that with the service manager and then all of the associated options in each of the above components, it’s not that easy. When you put testing on top of that, it can seem like a lot of work for not that much gain. However, from recent experience, it all fits together really well. How are you getting along with it?

  • bhagi

    hello,

    is this the whole source code. how the output looks. any picture on above example

    • http://www.matthewsetter.com/ Matthew Setter

      It’s been a while since this post. I’ll review it and add a Gist for it.

  • bhagi

    helo,
    without zfcbase, zfcuser this wont work .

    • http://www.matthewsetter.com/ Matthew Setter

      Did you mean that I can’t use ZfcUser without adding ZfcBase as a requirement as well? I don’t quite understand the comment.

  • Ashish

    Hi,
    I would like to open register form in edit mode based on current user ID , for now it is redirecting to sign out link.
    is that possible?

  • Welington

    Matthew, thank you for the great work on the blog. It is being really useful for me. I just have a question about the setData() method on the controller. If I have 2 fieldsets in a form and I call the setData() method on the controller, it automatically recognize and associate the fields from data and the fieldsets? Or it would work like the view helpers where I have to specify the fieldset I want – $this->formElement($form->get(‘fieldsetName’)->get(‘fieldName’) -. And about the entities? I need to have one entity for each fieldset or can I have one entity for multiple fieldsets? Thank you!

    • http://www.matthewsetter.com/ Matthew Setter

      To be honest I’ve not used fieldsets a lot, but I think, from memory, you reference the entity, on the fieldset on the form, as you’ve described. Would having a base fieldset, which contained the entity work, so that it could then be reused? I’m not sure I quite understand what you’re asking.