Validating Zend_Forms using model objects

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?