Custom Zend DB Table Row Class with Validation and Filtering

This is a follow up post to my previous post about creating a custom Zend Db Table base class. As mentioned in the previous post the Zend Framework implementation of their database abstraction layer is an implementation of the Table Data Gateway pattern and the Row Data Gateway pattern. This implementation is well tested and provides a great amount functionality to help you get your application up and running fast (Bill you’re a legend). Some may say that it’s RAD but there are still some important aspects of the “model” missing from the current Zend_Db_Table and Zend_Db_Table_Row implementation.  The Zend Framework team will freely admit this because as they say, they have left the implementation up to the developer.

As a rule when I create my “models” I use a simple extension the Zend_Db_Table_Row class.  For example I might have a “User” object that extends my own implementation of Zends default row class that I’ve named Swink_Db_Table_Row.  Of course my class would extend the normal Zend row class in the following manner.

 
class Swink_Db_Table_Row extends Zend_Db_Table_Row_Abstract {}

My final “model” class would then extend the custom base class like so:

 
/**
* A basic User model class 
*/
class User extends Swink_Db_Table_Row {
 
   /**
   * Add custom row related methods here
   */  
 
   /**
   * Check the see if this user is active. Checks the
   * bit field to see if they are active.
   * @return bool
   */
   public function isActive() {
      return (bool) $this->isActive;
   }
}

I would be using the Zend autoloader and would have setup the autoloader to use the include path to my library directory. This would have been done in my bootstrap in a manner similar to:

 
// setup the library path as a constant
define("SWINK_LIBRARY_PATH", SWINK_DOCUMENT_ROOT . "/library");
 
// setup the basic include paths
set_include_path(implode(PATH_SEPARATOR, array(
    SWINK_LIBRARY_PATH,
    get_include_path()
)));

When I talk about this class in terms of a “model”, I’m talking about it in the context of the Model View Controller (MVC) design pattern used by the Zend Framework (and many others).  The way that I see it the role of a model should cover:

  • Modelling the data from persistent storage
  • Validating input of model data
  • Filtering input/output of model data

There is already a big green tick on the first item on the list due some legendary work from the ZF team. The reason that the model should at least have the option of validating it’s data comes down to the fact that it’s the final gateway between the outside and the internal data source. Some of you out there may be saying, “but hang on I can validate and filter with my Zend_Form implementation”. Yes you could, although as your site grows I’m sure that you’ll be using models in all sorts of situations that my not require a form (such as a web services etc). I call these “naked” models and they should at least “have the option” of using an internal validation system. I say “have the option” as there will certainly be cases where you can have Zend_Form validate your data and you don’t want to double dip it in the models own internal validation.

The current implementation of the model (DB_Table_Row) class does not handle the validation and filtering aspects of a model.  Enter Zend_Filter_Input.  From the manual:

Zend_Filter_Input provides a declarative interface to associate multiple filters and validators, apply them to collections of data, and to retrieve input values after they have been processed by the filters and validators. Values are returned in escaped format by default for safe HTML output“.  

That sounds like something that we can use in our models and if you look at the internals behind Zend_Form then you’ll see that this is what is used there too. The code is nothing surprising. We’ll add an the following method signatures to assist us:

  • Variable Zend_Filter_Input input
  • Variable array validators
  • Variable array filters
  • Method Object Model::setValidators(array validators)
  • Method Object Model::setFilters(array filters)
  • Method boolean Model::isValid(array data)
  • Method int Model::id()
  • Method Boolean Model::save(array data)

I usually add several more helper methods such as “bool hasFilters()” and “bool hasValidators()” etc but they are documented further in the code below.  We don’t necessarily need to take advantage of the “setValidators” and “setFilters” methods directly as I will usually pass in an array of validators and filters (or an Zend_Config object).  If you use this model somewhere else which has a form object then it might be an idea to store the validators and filters in a separate config object that can be passed into the form when that’s used, and into the “naked” model directly when that’s being used on its own.

For example the contructor may look like:

/**
* Optionally setup validators and filters
*
* @param array $config
*/
public function __construct($config = array()) {
   // check out the config
   if ($config instanceof Zend_Config) {
      $config = $config->toArray();
   }
   // check for other items
   if (!empty($config)) {
      // check the config for sections that need to be loaded
      if (isset($config['filters'])) {
         $this->addFilters($config['filters']);
         unset($config['filters']);
      }
      if (isset($config['validators'])) {
         $this->addValidators($config['validators']);
         unset($config['validators']);
      }
   }
   // call the parent 
   parent::__construct($config);
}

Class Usage

The easiest way that I’ve found to use the validation with the model is just to create a new instance and call the isValid() method before saving. For instance:

 
// arbitrary data from somewhere
$data = array('name' => "Johnny", 'email' => 'johnny@email.com');
// load up a common config with validators etc 
$config = new Zend_Config_Xml("/configs/models/user.xml");
// instantiate a new user
$user = new User($config);
 
// method 1 to see if valid
 
// set the data first
$user->setData($data);
// see if its valid prior to saving
if ($user->isValid()) {
   $user->save();
}
 
// method 2 to see if valid
 
// pass data in directly
if ($user->isValid($data)) {
   $user->save();
}
 
// method 3 to see if valid
 
// set the properties individually
$user->name = "Johhny";
$user->email = "johhny@email.com";
 
// pass data in directly
if ($user->isValid()) {
   $user->save();
}
 
// method 4 to see if valid
 
// if you're using the custom table class from my other post
$user = Users::findByEmail('johnny@email.com');
// or
$user = Users::findById(7);
 
// more complex example
if ($user->hasValidators() && $user->isValid($data)) {
   $userId = $user->save();   
}

Source code

It’s quite easy and just provides that extra layer of protection and assurance. So the final base row class to assist us with our self contained model would look like:

<?php
/**
* A basic subclassing of the Row object to provide validation and filtering
* as well as some convenience functions
*/
class Swink_Db_Table_Row extends Zend_Db_Table_Row_Abstract {
   /**
   * An array of filters
   *
   * @var array
   */
   protected $_filters = array();
 
   /**
   * An array of validators
   *
   * @var array
   */
   protected $_validators = array();
 
   /**
   * The data cage for this model
   *
   * @var Zend_Filter_Input
   */
   protected $_input;
 
   /**
   * Optionally setup validators and filters
   *
   * @param array $config
   */
   public function __construct($config = array()) {
      // check out the config
      if ($config instanceof Zend_Config) {
         $config = $config->toArray();
      }
      // check for other items
      if (!empty($config)) {
         // check the config for sections that need to be loaded
         if (isset($config['filters'])) {
            $this->addFilters($config['filters']);
            unset($config['filters']);
         }
         if (isset($config['validators'])) {
            $this->addValidators($config['validators']);
            unset($config['validators']);
         }
      }
      // call the parent 
      parent::__construct($config);
   }
 
   /**
   * Wrapper for the setFromArray method
   * @param array $data
   */
   public function setData(array $data) {
      $this->setFromArray($data);
      return $this;
   }
 
   /**
   * Shorthand method to set data and then save
   * @param array $data
   * @return Swink_Db_Table_Row
   */
   public function save(array $data = array()) {
      // if there is data then set it
      if (!empty($data)) {
         $this->setData($data);  
      }     
      return parent::save();
   }
 
   /**
   * Function for validating data according to the declared rules.
   * Uses Zend_Filter_Input
   *     
   * @param boolean setData set the filtered data as the internal data
   * @return boolean
   */
   public function isValid($setEscapedData = false) {
      // run the validation
      $valid = $this->getInput()->isValid($this->_data);
      // run the check
      if (true == $valid && $setEscapedData) {
         // set the filtered data if requested
         $this->setFromArray($this->getInput()->getEscaped());
      }
      return $valid;
   }
 
   /**
   * Lazy load a Zend_Input_Filter instance
   * @return Zend_Filter_Input
   */
   protected function _getInput() {
      if (!$this->_input instanceof Zend_Filter_Input) {
         $this->_input = new Zend_Filter_Input($this->getFilters(), $this->getValidators());
      }
      return $this->_input;
   }
 
   /**
   * Checks to see if the model has filters
   *
   * @return boolean
   */
   public function hasFilters() {
      return (bool) !empty($this->_filters);
   }
 
   /**
   * Gets the model filters
   *
   * @return array
   */
   public function getFilters() {
      return $this->_filters;
   }
 
   /**
   * Checks to see if this class has validators
   *
   * @return array
   */
   public function hasValidators() {
      return (bool) !empty($this->_validators);
   }
 
   /**
   * Gets this models validators
   *
   * @return array
   */
   public function getValidators() {
      return $this->_validators;
   }
 
   /**
   * A public setter to add a new validator.  This validator may
   * be a string to represent a class or it may be an array with
   * containing a validator class name and some params. Setup for
   * chaining so by returning the class.
   *
   * @param mixed string|array
   * @return Translink_Model_Row
   */
   public function addValidator($name, $validator) {
      $this->_validators[$name][] = $validator;
      return $this;
   }
 
   /**
   * Add more than one validator to the field object
   *
   * @param array
   * @return Translink_Model_Row
   */
   public function addValidators($validators) {
      $this->_validators = array_merge($this->_validators, (array) $validators);
      return $this;
   }
 
   /**
   * Add a filter to the field object
   *
   * @param string
   * @return Translink_Model_Row
   */
   public function addFilter($name, $filter) {
      $this->_filters[$name][] = $filter;
      return $this;
   }
 
   /**
   * Add more than one filter to the field object
   *
   * @param string
   * @return Translink_Model_Row
   */
   public function addFilters($filters) {
      $this->_filters = array_merge($this->_filters, (array) $filters);
      return $this;
   }
 
   /**
   * A convenience function to return the id
   * @return mixed
   */
   public function id() {     
      return $this->_data[current((array)$this->getTable()->getPrimaryKey())];
   }
}

I hope that you’ve found this interesting and informative. It’s not rocket science but it’s a nice addition to the normal row class that will help to make model validation more flexible across a variety of situations. It will also keep things more DRY by putting your validation and filtering rules into a Zend_Config file that can be accessed by both your “naked” models and your Zend_Form objects that use the models.

Source code download

You can download the code snippets from this tutorial here.

Comments are closed.