Bind Zend_Form to the model

Zend Frame­work has great com­pon­ent — Zend_Form which can be used to cre­ate a reusable form. So you can cre­ate a com­mon form, for example, for both new user regis­tra­tion and user pro­file editor. In most cases such form will rep­res­ent your model. So you’ll cre­ate a lot of repet­it­ive code for filling model’s prop­er­ties with form val­ues. Instead of this you can write a bind­ing meth­ods so your form will know how to bind itself to your model…

I use Doc­trine PHP ORM for BLL mostly. But as some people dis­like to mix Zend Frame­work with third party lib­rar­ies, 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 bind­ings. For this pur­poses I’ll cre­ate des­cend­ant class My_Form with array of bind­ings and method to add bind­ings 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 bind­ings with code sim­ilar to:

$form->bind('username', 'username');
$form->bind('password', 'md5secret');

A little explan­a­tion. First argu­ment is a form element’s name, as it was spe­cified to Zend_Form::addElement() method. Second is your model’s prop­erty name (as described in Doctrine_Record::setTableDefinition()). The last one, optional, argu­ment is a func­tion name to be used to mangle value of form field. For example you have a select input with coun­tries, where value of option is a coun­try code. So you want to bind a coun­try object to prop­erty of your model, so you can do some­thing sim­ilar to:

$form->bind('country', 'Country',
            array('Country', 'getByCode'));

To reduce amount of lines and make bind­ing looks nicer, I also added a wrap­per 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 cre­ate 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 fol­low­ing scheme:

---
User:
  columns:
    id: integer(8)
    login: string(32)
    password: string(32)
    password: string

Let’s cre­ate a My_Form_User class to rep­res­ent 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 call­back func­tion for pass­word ele­ment to strrev(). This is just for an example, don’t try to find a deep mean­ing in that defin­i­tion :)) After we have defined everything we need, we can cre­ate a UserController. So upon post request we can val­id­ate passed val­ues 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 cre­ate an editAction() to allow our user to change his e-mail or pass­word. But as we don’t want to allow user change his user­name we can simply remove that ele­ment 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 attach­ment to this post and run it. The example applic­a­tion uses mock data­base adapter so you don’t need to setup data­base tables first or spe­cify a con­nec­tion string to the Doctrine_Manager.

  • del.icio.us
  • Google Bookmarks
  • Identi.ca
  • Twitter
  • Technorati
  • Digg
  • Slashdot
  • Facebook
  • MisterWong
  • Reddit
  • StumbleUpon
  • Mixx
  • HelloTxt
  • LinkedIn
  • PDF
  • email
  • Print
This entry was posted in Zend Framework and tagged , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>