Zend Framework has great component — Zend_Form which can be used to create a reusable form. So you can create a common form, for example, for both new user registration and user profile editor. In most cases such form will represent your model. So you’ll create a lot of repetitive code for filling model’s properties with form values. Instead of this you can write a binding methods so your form will know how to bind itself to your model…
I use Doctrine PHP ORM for BLL mostly. But as some people dislike to mix Zend Framework with third party libraries, I’ll show examples of usage with both Doctrine_Record and Zend_Db_Table.
First of all we’ll need to extend Zend_Form to keep map of bindings. For this purposes I’ll create descendant class My_Form with array of bindings and method to add bindings to that map:
class My_Form extends Zend_Form
{
// ...
/**
* Binding's registry.
*
* Multi-dimensional array. Each element of this array is identified by
* string name of element of this form and value is an array where first
* element is model's property name and second is optional callback
* function or method.
*
* Example:
*
* $this->_bindings['film_name'] = array('name', null);
* $this->_bindings['actors'] = array('actors', 'prepareActorsList');
*
*
* @see My_Form::bind()
* @var array
*/
protected $_bindings = array();
/**
* Add binding to the registry
*
* If you want to mangle value which would be passed to model's property
* you can specify $callback function which will be called with value of
* element passed as first argument and it's return value will be then
* used instead of form element's value.
*
* Example:
*
* function prepareActorsList($value)
* {
* return serialize(explode(';', $value));
* }
*
*
* @param string $elementName
* @param string $modelProperty
* @param mixed $callback (optional)
* @return My_Form Self-reference
*/
public function bind($elementName, $modelProperty, $callback = null)
{
$this->_bindings[$elementName] = array($modelProperty, $callback);
return $this;
}
// ...
}
So now I can add bindings with code similar to:
$form->bind('username', 'username');
$form->bind('password', 'md5secret');
A little explanation. First argument is a form element’s name, as it was specified to Zend_Form::addElement() method. Second is your model’s property name (as described in Doctrine_Record::setTableDefinition()). The last one, optional, argument is a function name to be used to mangle value of form field. For example you have a select input with countries, where value of option is a country code. So you want to bind a country object to property of your model, so you can do something similar to:
$form->bind('country', 'Country',
array('Country', 'getByCode'));
To reduce amount of lines and make binding looks nicer, I also added a wrapper for Zend_Form::addElement() and My_Form::bind() methods:
class My_Form extends Zend_Form
{
// ...
/**
* Wrapper to allow specify model binding.
*
* Optional $binding may be specified either as a string (in this case it's
* value will be used as model's property name) or as an array where first
* element is a model's property name and second is a callback function.
*
* @see My_Form::bind()
* @see Zend_Form::addElement()
* @param string|Zend_Form_Element $element
* @param string $name
* @param array|Zend_Config $options
* @param mixed $binding
* @return My_Form Self-reference
*/
public function addElement($element, $name = null, $options = null, $binding = null)
{
parent::addElement($element, $name, $options);
if (null !== $binding) {
if (is_array($binding)) {
list($model_prop, $callback) = $binding;
} else {
$model_prop = $binding;
$callback = null;
}
$this->bind($name, $model_prop, $callback);
}
return $this;
}
// ...
}
And now the “magic” part which will fill our Doctrine_Record based model:
class My_Form extends Zend_Form
{
// ...
/**
* Fills $model's fields with filtered values from form.
*
* @param Doctrine_Record $model
* @return Doctrine_Record
*/
public function fill(Doctrine_Record $model)
{
foreach ($this->getElements() as $name => $element) {
if ( ! array_key_exists($name, $this->_bindings)) {
continue;
}
list($prop, $callback) = $this->_bindings[$name];
$model->$prop = (null !== $callback)
? call_user_func($callback, $element->getValue())
: $element->getValue();
}
return $model;
}
// ...
}
Or if you are using Zend_Db_Table based BLL, you can simply create an array of data to be passed to Zend_Db_Table’s insert(), update(), etc.
class My_Form extends Zend_Form
{
// ...
/**
* Prepares an array of data suitable for Zend_Db_Table model
*
* Same as {@link My_Form::fill()} but creates an array with form elements'
* data suitable for passing to the methods of {@link Zend_Db_Table} like
* {@link Zend_Db_Table::insert()} etc.
*
* @return array
*/
public function getModelData()
{
$data = array();
foreach ($this->getElements() as $name => $element) {
if ( ! array_key_exists($name, $this->_bindings)) {
continue;
}
list($prop, $callback) = $this->_bindings[$name];
$data[$prop] = (null !== $callback)
? call_user_func($callback, $element->getValue())
: $element->getValue();
}
return $data;
}
// ...
}
That’s all. Now let’s see how it works on examples. Let’s assume we have a model of following scheme:
---
User:
columns:
id: integer(8)
login: string(32)
password: string(32)
password: string
Let’s create a My_Form_User class to represent this model:
class My_Form_User extends My_Form
{
public function init()
{
$this->addElement(
'text',
'username',
array(
'label' => 'Username',
'required' => true
),
'login'
)->addElement(
'text',
'email',
array(
'label' => 'E-mail',
'required' => true,
'validators' => array('EmailAddress')
),
'email'
)->addElement(
'password',
'password',
array(
'label' => 'Password',
'required' => true
),
array('password', 'strrev')
)->addElement(
'submit',
'submit',
array(
'label' => 'Submit!'
)
);
}
}
As you can see I set a callback function for password element to strrev(). This is just for an example, don’t try to find a deep meaning in that definition :)) After we have defined everything we need, we can create a UserController. So upon post request we can validate passed values to the form and bind form to the model if everything is OK:
class UserController extends Zend_Controller_Action
{
// ...
public function registerAction()
{
$form = new My_Form_User();
// ...
if ($this->getRequest()->isPost() && $form->isValid($_POST)) {
$user = new User();
$form->fill($user);
// ...
}
$this->view->assign('form', $form);
}
// ...
}
Also we want to create an editAction() to allow our user to change his e-mail or password. But as we don’t want to allow user change his username we can simply remove that element out of the form with $form->removeElement('username'). So we’ll define this action like:
class UserController extends Zend_Controller_Action
{
// ...
public function editAction()
{
// ...
$form = new My_Form_User();
$form->removeElement('username');
// ...
if ($this->getRequest()->isPost() && $form->isValid($_POST)) {
// Assume you have received User model before
// (e.g. by current user's id)
$form->fill($user);
// ...
}
$this->view->assign('form', $form);
}
// ...
}
That’s it. And that’s all. To see it in action you can grab an attachment to this post and run it. The example application uses mock database adapter so you don’t need to setup database tables first or specify a connection string to the Doctrine_Manager.