Validating Zend_Forms using model objects

May 7, 2009 – 10:19 pm Tags: , , ,

Zend_Forms, models, validation and how they all work together is a tricky topic. There are many opinions and many ways to do it.

This time I’ll show you what I think could be the answer to validating forms without having to duplicate validation code both in a model class and in your form.

1: Create a model interface

For this to work nicely, we’ll need all our models to implement the same interface. This will allow us to use the models without having to worry if it’s compatible with what we’ll make after this.

Let’s use a simple interface with four methods:
Download interface code

interface CU_Model_IModel {
    /**
     * Are the properties of this model valid?
     * @return bool
     */
    public function isValid();
 
    /**
     * Get any error messages related to validation
     *
     * Must return an array of key => value pairs where the
     * key is the property of the model the message is for
     * and the value is the actual message
     *
     * @return array
     */
    public function getMessages();
 
    /**
     * Set a public property of the model
     * @param string $property
     * @param mixed $value
     */
    public function set($property, $value);
 
    /**
     * Get a public property of the model
     * @param string $property
     * @return mixed
     */
    public function get($property);
}

This interface will do nicely for our validation purproses.

2: Create a validator

The approach we’ll use is the second one I described in an older post about form validation and models:

We will create a special validator, which task is to validate a value using a model class.

The basic idea is that we will provide the validator with the model instance, and the property the value will represent. Then, when the validator is used, it will set the value in the model and see if it validates – this is why our model interface has methods for validating, getting messages and setting values in a specific way.

Download validator code

/**
 * Validates a value using a model
 */
class CU_Validate_Model extends Zend_Validate_Abstract {
    const NOT_VALID = 'notValid';
 
    protected $_messageTemplates = array(
        self::NOT_VALID => 'Value is not valid'
    );
 
    private $_model = null;
    private $_property = null;
 
    public function __construct(CU_Model_IModel $model = null, $property = null) {
        if($model !== null) {
            $this->setModel($model);
        }
 
        if($property !== null) {
            $this->setProperty($property);
        }
    }
 
    public function setModel(CU_Model_IModel $model) {
        $this->_model = $model;
    }
 
    public function setProperty($property) {
        $this->_property = (string)$property;
    }
 
    public function isValid($value) {
        if($this->_model === null || $this->_property === null) {
            throw new RuntimeException('The model or property was not set before attempting to validate');
        }
 
        $this->_setValue($value);
        $this->_model->set($this->_property, $value);
 
        if($this->_model->isValid()) {
            return true;
        }
 
        $errors = $this->_model->getMessages();
        if(array_key_exists($this->_property, $errors)) {
            $this->_error();
            return false;
        }
 
        return true;
    }
}

The validator is quite simple as you can see. It just checks in the isValid method whether the value makes the model give an error for it.

For it to work correctly, we must either pass the model and property as parameters in the constructor, or later call setModel or setProperty methods.

Next, we’ll put it all together by creating a simple form and a model and use that to validate the form!

3: Putting it all together

First, let’s create a simple model class we will use as the validator model.

class MyModel implements CU_Model_IModel {
    private $_properties = array();
    private $_errors = array();
 
    public function isValid() {
        $valid = isset($this->_properties['foo']) && $this->_properties['foo'] == 'bar';
 
        if(!$valid) {
            $this->_errors['foo'] = 'Foo is not bar';
        }
        else {
            $this->_errors = array();
        }
 
        return $valid;
    }
 
    public function getMessages() {
        return $this->_errors;
    }
 
    public function set($property, $value) {
        $this->_properties[$property] = $value;
    }
 
    public function get($property) {
        return $this->_properties[$property];
    }
}

The only validation this class does is check that we have set a property called ‘foo’ and that foo’s value is ‘bar’ – not necessarily a very realistic model but will suffice for our example.

Now, let’s create a form using the model and validator:

$model = new MyModel();
 
$form = new Zend_Form();
$form->addElement('text', 'foo', array(
    'validators' => array(
        new CU_Validate_Model($model, 'foo')
    ),
    'required' => true
));

There. Now we can test it:

//This will return true:
$form->isValid(array('foo' => 'bar'));
 
//This will return false:
$form->isValid(array('foo' => 'xyz'));

In closing

Using this approach you can easily re-use your models in form validation logic. You don’t have to use the very same interface I showed here as long as your own model classes provide some way to set values and check their validity.

By using this approach you reduce code-duplication you normally would need in order to use Zend_Validate objects in forms and your own validation code in models.

Do you have a better idea? Feel free to share it in the comments!

More posts on models:

How to use models as criteria objects for querying the database?
How to decouple models from the database?

Share this:
  1. 10 Responses to “Validating Zend_Forms using model objects”

  2. Couldn’t you just use the form objects for validation inside of the models?

    By Rob on May 7, 2009

  3. You could, but what does the form have to do with the model? Not much in my opinion – unless your model represents a form.

    Also, you might have a single form which is used to fill values for multiple models.

    There is also the performance point of view: Zend_Form objects can be expensive to create as they have a ton of other object instances inside them.

    By Jani Hartikainen on May 7, 2009

  4. Ah good call, I was primarily thinking in the most simple implementations.

    By Rob on May 7, 2009

  5. Hi, thanks for this great idea.

    Do you can publish a example with two or more elements? Because I can’t do this with your code.

    One question: MyModel->isValid will be called for each element? But this logic is to validade all model properties. Is it correct?

    Best regards.

    By Luiz Fernando on May 9, 2009

  6. Luiz, to validate more elements, you simply create another form element and add another model validator as that one’s validator.

    As for isValid… Yes, in this case it is correct behavior that it revalidates everything. Of course, feel free to modify the model interface to provide a method for validating just one property if you want. This is mostly just a demonstration of the idea, you can implement it yourself differently if you want.

    By Jani Hartikainen on May 9, 2009

  7. Hi Jani

    Thanks very much for this example. I have been trying to work out a good approach for validation reuse within my Soap implementation (I’m very new to Zend) and this has been very useful.

    cheers
    Elton

    By Elton Scott on Oct 1, 2009

  8. Jani,

    I’ve been researching moving validation in ZF as close to the model as possible for some days now. Particularly once models are well designed it’s very unnerving to put validation elsewhere. Domain Logic/Models are the heart of software; everything else should, for lack of a better phrase, float around them. Your post really helped validate, no pun intended, the solution I’ve been working on in regards to Zend Framework. Great thoughts and thanks for sharing!

    Johnny

    ps. I know it’s a older post. If there are new developments in this regard please make them known. Thanks again!

    By Johnny on Nov 17, 2010

  1. 3 Trackback(s)

  2. May 8, 2009: Подборка линков 8.05 « Di Sole
  3. May 8, 2009: Подборка линков 8.05 « Di Sole
  4. May 9, 2009: Validating Zend_Forms using model objects | Dailytuts.net

Post a Comment

You can use some HTML (a, em, strong, etc.). If you want to post code, use <pre lang="PHP">code here</pre> (you can replace PHP with the language you are posting)