In Zend Framework 2, so much has been changed, updated and improved - with Forms receiving a lot of attention. In this post, I show you a simple, flexible and powerful approach to compose and use them everywhere in your applications.
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
- src
- ContentCore
- Entity
- 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,
)
);
}
}
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;
},
// …
}
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
Join the discussion
comments powered by Disqus