PHP: a simple model mapper with Zend_Db_Table

Using Zend_Db_Table to access database is a good way to retreive data. However, the pattern Zend_Db_Table implements (table data gateway) has, IMHO, a major drawback: it does not provide abstraction of table’s fields names.

This article describes a simple solution that I use…

Introdution: the problem

Providing tables’ fields names abstraction is sometime also called “row mapping”. The purpose is to avoid using column’s name in the whole application to avoid dramatic impacts when the fields’ names change.

Some peopol does such mapping in the “service” layer. But this add some overhead to your application because the “service” layer have to do loop throught all record set to convert it to a “local” array with custom columns names.

On my side, I prefer to make a little mapping mecanism which address the columns names dependencies. Moreover, the method I use brings another bonus: access to data is done thought getter and setter and no more directly (or via __get() and __set()). This way, the code does not rely on columns names and it allows to add some logics to thoses methods if needed.

A (simple) solution

overriding method __call() of Zend_Db_Table_Row

class Models_RowMapper extends Zend_Db_Table_Row
{
    /**
     * Tries to map column name with setter and getter for
     * all methods that begins with 'set' or 'get'.
     * It uses getColumnName() method of the table class.
     * 
     * If the call does not begin with 'get' or 'set', we pass
     * the call to parent::__call()
     * @see Db/Table/Row/Zend_Db_Table_Row_Abstract#__call($method, $args)
     */
    public function __call($methodName, $methodParams)
    {
        // first: separate get/set prefix and mapper name
        $prefix = substr($methodName, 0, 3);
        $name = $this->getTable()->getColumnName(substr($methodName, 3));
 
        // handle get/set or call parent method
        if ($prefix == 'get') {
            return $this->$name;
        } elseif ($prefix == 'set') {
            $this->$name = $methodParams[0];
            return $this; // provides fluid interface
        } else {
            return parent::__call($methodName, $methodParams);   
        }
    }
}

in the following examples, we consider a “User” table with the followings columns: ID, NAME, FORENAME, EMAIL.

class Models_Base extends Zend_Db_Table_Abstract
{    /**
     * array to map database table columns to setter and getter methods
     * @var array
     */
    protected $_columnsMapper = array();
 
    /**
     * wrapper to parent::__construct that set a custom Row class
     * 
     * @see Zend/Db/Table/Abstract#__construct
     * @param array $config
     */
    public function __construct($config = array())
    {
        parent::__construct($config);
        $this->setRowClass('Models_RowMapper');
    }
 
    /**
     * get a column name by its mapper keyword (default: strtoupper($name))
     * @param $name mapper keyword
     * @return string
     */
    public function getColumnName($name)
    {
        if (array_key_exists($name, $this->_columnsMapper)) {
            return $this->_columnsMapper[$name];
        } else {
            return strtoupper($name);
        }
    }
}

 

Going further

Now imagine that the application is near finished and the DBA tell you that the columns of the “User” table has been renamed to : USER_NAME, USER_FORENAME and USER_EMAIL.

User the above solution, you just need to set the “_columnsMapper”  attributs of “Models_User” to the following:

protected $_columnsMapper = array('Name'     => 'USER_NAME',
                                  'ForeName  => 'USER_FORENAME'
                                  'Email'    => 'USER_EMAIL');

So now for example, “setName” will map the “USER_NAME” attribut.

Final word

As usual, please don’t hesitate to correct me. Also, if something is not clear, I can try to correct it.

Leave a Reply

Your email address will not be published. Required fields are marked *


*