designing4u.de Yet Another Coding Blog

29Jul/110

Conditional validation of shipping address using Zend sub forms

Zend Framework documentation gives you an excellent example on how to use sub forms. Sub forms let you split the logic of your application into smaller parts, validate it on demand and after collecting all the information validate the whole entity. Pretty cool huh? Lately I was implementing a shopping cart. In the last part of the check out process the user has to provide the billing address and shipping address. Usually they are the same but sometimes they differ. Here is how I solved the conditional validation of the form fields for orders where shipping address differs.

For the brevity I removed some of the fields from the form and left only first name and last name just to demonstrate how your form should look like. I also removed validations, filters and decorators. In your application you should add all fields that are necessary in your checkout process and attach the corresponding validations to those fields.

The code is pretty simple. In the init method you have to initialize sub form for billing and sub form for shipping. You then add the fields to the sub forms, attach validations to the billing forms and the checkbox to the shipping sub form.

<?php
/**
 * Application_Form_Address class
 *
 * Class is responsible for collecting the buyers address.
 *
 * @author Wojciech Gancarczyk <gancarczyk@gmail.com>
 */
class Application_Form_Address extends Zend_Form
{
    /**
     * Initializes the form and sets the elements.
     *
     * @return void
     */
    public function init()
    {
        $billing = new Zend_Form_SubForm();
        $this->addFields($billing)->attachValidators($billing);
 
        $shipping = new Zend_Form_SubForm();
        $differs = new Zend_Form_Element_Checkbox('differs');
        $differs->setLabel('Shipping differs')->setValue('0');
        $shipping->addElement($differs);
        $this->addFields($shipping);
 
        $this->addSubForms(array(
            'billing' => $billing,
            'shipping' => $shipping
        ));
 
        $submit = new Zend_Form_Element_Submit('submit');
        $this->addElement($submit);
    }
 
    /**
     * Add fields to the sub form.
     *
     * @param Zend_Form_SubForm $form
     * @return Application_Form_Address
     */
    public function addFields(Zend_Form_SubForm $form)
    {
        $firstname = new Zend_Form_Element_Text('firstname');
        $firstname->setLabel('Firstname');
        $form->addElement($firstname);
 
        $lastname = new Zend_Form_Element_Text('lastname');
        $lastname->setLabel('Lastname');
        $form->addElement($lastname);
 
        return $this;
    }
 
    /**
     * Attach validators to the fields in the sub form.
     *
     * @param Zend_Form_SubForm $form
     * @return void
     */
    public function attachValidators(Zend_Form_SubForm $form)
    {
        $form->getElement('firstname')->setRequired(true);
        $form->getElement('lastname')->setRequired(true);
    }
 
    /**
     * Overwrites the parent method and attaches
     * conditional validators only if shipping address
     * differs.
     *
     * @param array $data
     * @return bool
     */
    public function isValid($data)
    {
        $this->populate($data);
 
        $shipping = $this->getSubForm('shipping');
        if ((int) $shipping->getElement('differs')->getValue() === 1) {
            $this->attachValidators($shipping);
        }
 
        return parent::isValid($data);
    }
 
    /**
     * Process the data.
     *
     * @return void
     */
    public function process()
    {
        // do something with the data
    }
}

The interesting part is the isValid method, which overwrites the method in the parent class. Before the validation will be delegated to the parent class, the data form the request is populated in the form. If the value of checkbox differs changed to 1 (meaning the user wants to provide a different shipping address), the validations are attached to the shipping form.

public function addressAction()
{
    $form = new Application_Form_Address();
    if ($this->_request->isPost()) {
        if ($form->isValid($this->_request->getPost())) {
            $form->process();
            // do something ...
        }
    }
    $this->view->assign(array('form' => $form));
}

As usually in your controller you would initialize the form, check if the request is a post request, validate the form and redisplay it if necessary. Go and try it out, if the differs checkbox won't be clicked, only the billing form will be validated, in other case both of the form will be validated.

Comments (0) Trackbacks (0)

No comments yet.


Leave a comment

(required)

No trackbacks yet.