20110328

Validation in Zend Framework model

The model part of the Zend Framework is very open. In fact, ZF doesn't help you at all with it, other than providing some classes that manage a DB connection. Zend_Db_Table is good and all, but if you want a real-life model you need to work from scratch.

Which is how it should be, really.

The model is the base for your application. It should have all the so called "business rules", and this includes, of course, validation. Now, ZF has some pretty neat validation classes that you can use. The problem is how to write the code only once for the model and for the forms (view).

I've asked this question and got one answer, and while it was a good option it didn't exactly suit my needs, so I'm proposing another solution here.

When you create a Zend_Form, you have a couple of ways of adding elements to it. One of them is to call, from the form's init method, the addElement method, and pass it an array of options that contain everything about the element, like this:

$username = $this->addElement('text', 'username', array(
  'validators' => array(
    'Alpha',
    array('StringLength', false, array(3, 20)),
  ),
  'label' => 'Username',
));

This is all good, but how can you share those validators with your User model? Well, the idea is to create an Entity class from which almost all the model inherits, which works like this:

abstract class Entity
{
  protected $_data = array();
  protected static $_validators = array();

  public function __set($name, $value)
  {
    // process validators, if any
    if (array_key_exists($name, self::$_validators))
    {
      foreach (self::$_validators[$name] as $v)
      {
        if (is_string($v))
          $v = array($v, NULL, NULL);
        if (!Zend_Validate::is($value, $v[0], $v[2]))
          throw new Exception('Invalid input');
      }
    }
    $this->_data[$name] = $value;
  }

  public function __get($name)
  {
    return $this->_data[$name];
  }
}

You can see we've overriden the __set and __get methods so we can access the $_data array directly. This allows for very clean code later, instead of writing a bunch of setters and getters which is tedious and boring. So, the User class would be something like this:

class User extends Entity
{
  protected $_data = array('id', 'username');
  protected static $_validators = array(
    'username' => array(
      'Alpha',
      array('StringLength', false, array(3, 20)),
    ),
  );
}

This way, when you have a $user object, you can simply set its attributes and validation will happen magically. So, what about the form? You can easily get the validators array with a public function in Entity:

abstract class Entity
{
  ...

  public function getValidators($name)
  {
    return array_key_exists($name, self::$_validators)
      ? self::$_validators
      : array();
  }
}

And, finally, in your form's init function:

$username = $this->addElement('text', 'username', array(
  'validators' => User::getValidators('username'),
  'label' => 'Username',
));

Now you can change the validators in one place, and your code looks clean and is maintainable.

Of course, you can apply the same idea to Zend Filters, using the Zend_Filter::filterStatic function. Filtering would happen before validation, of course, using a similar foreach loop.

1 comment: