On models in a Zend Framework application

13th December 2008

Let's talk about writing models that communicate with databases within a Zend Framework application. It's a popular topic at the moment as there's been a few threads recently on the ZF mailing lists about creating models.

The "is a" relationship

When working with models within Zend Framework, the simplest solution is to extend Zend_Db_Table_Abstract and Zend_Db_Table_Row_Abstract along these lines:


class Users extends Zend_Db_Table_Abstract
{
    protected $_name 'users';
    protected $_rowClass 'User';
    
    public function fetchAllInLastNameOrder()
    {
        return $this->fetchAll(null, array('last_name''first_name'));
    }
    
    public function fetchUserById($id)
    {
        $id = (int)$id;
        return $this->_fetchRow('id = '$id);
    }
}

class User extends Zend_Db_Table_Row_Abstract
{
    public function name()
    {
        return $this->first_name ' ' $this->last_name;
    }
}

This works fairly well for models where there is a one to one mapping to the underlying database table. It starts to falls down if a given model needs to know about two or more database tables to do its work.

The knack is to avoid using the functions defined in Zend_Db_Table_Abstract within your models or views as this will lead towards fat controllers that are too aware of the underlying database. This gets quite hard as it's much easier to start putting model logic into the controllers with this system, especially when it comes to choosing if you need to insert or update a record. A sure sign that you have this problem is that your controllers create SQL statements or Zend_Db_Table_Select objects.

To try and avoid these problems, there have been discussions on the ZF mailing lists for a little while now about using a "has a" relationship from the model object to the Zend_Db_Table object. Bill Karwin has long been a proponent of this idea and recently there's been much discussion about it. The question is: how do you implement it in practice without ending up writing lots of code for the sake of "cleanliness"?

I've started a new ZF project recently where I've started playing with these ideas and this is my first attempt at it, so I thought I'd share where I had got to in my thinking.

The "has a" relationship

When implementing this relationship we are trying to hide the fact that we are using Zend_Db_Table from the consumers of the models. The basic idea is that we have a gateway class that holds a protected $_table variable that is an instance of a Zend_Db_Table object. When fetching data, the gateway object will return either a single object or an array of objects of a type which I've not-very-imaginatively named "StandardObject".

This is a picture of the various objects and how they relate to each other:

OnModels.png

To get this working, I first mapped out how I want the objects to work via a unit test and then built the classes behind it. I wouldn't call it TDD though as I didn't write tests for all the internals as I went along. This is the basic API that I want my consumers to use:


$userGateway = new UserGateway();

// insert a row
$data = array();
$data['first_name'] = 'John';
$data['last_name'] = 'Smith';
$row $userGateway->setFromArray($data);

// update a row
$data = array();
$data['id'] = 1;
$data['first_name'] = 'Rob';
$data['last_name'] = 'Allen';
$row $userGateway->setFromArray($data);

// fetch a single user
$row $userGateway->fetchUserById(1);

// fetch all users
$rows $userGateway->fetchAllInLastNameOrder();

// delete a user
$result $userGateway->deleteById(1);

The general idea is that I can write specific functionality into the UserGateway class as I need it. Note also that the row objects returned from the gateway class methods behave a little like ValueObjects only. As they are not attached to the database though, they cannot "save" themselves. (I couldn't find the "correct" design pattern name, which probably means, that my design is wrong!) This means that all interaction with the database is via the gateway class which makes this design similar in some ways to the Table Module design pattern.

The implementation is fairly involved, so get ready for lots of code!

Firstly we need the Gateway class:


class UserGateway extends App_Model_Db_StandardGateway 
{
    protected $_tableName 'UsersTable';
    protected $_rowClass 'UserObject';
    
    public function fetchUserById($id)
    {
        $id = (int)$id;
        return $this->_fetchRow('id = '$id);
    }
    
    public function fetchAllInLastNameOrder()
    {
        $orderBy=array('last_name''first_name');
        return $this->fetchAll($orderBy);        
    }
}

The UserGateway contains the specific methods for this model and has a couple of protected variables that are used to set up the system. We also need a table class, UsersTable that inherits from App_Db_Table_Standard which inherits from Zend_Db_Table_Abstract.


class UsersTable extends App_Db_Table_Standard
{
    protected $_name 'users';
}

Obviously we are going to have many gateway models, so it makes sense that common functionality will be in a parent class, which I've called App_Model_Db_StandardGateway. This is the big class and we'll look at it in chunks:


class App_Model_Db_StandardGateway
{
    /**
     * @var App_Db_Table_Abstract
     */
    protected $_table null;
    protected $_tableName null;
    protected $_rowClass 'App_Model_StandardObject';

    public function __construct()
    {
        if (empty($this->_tableName)) {
            throw new Exception('You must specificy _tableName');
        }
        $this->_table = new $this->_tableName();
    }

Every gateway has a $_table property which is instantiated in the constructor. The name of the table class to instantiate is stored in the $_tableName property which is set in the child class. Finally, we have a $_rowClass property which will be used to hold each row object when the data is retrieved from the database. Note that it defaults to App_Model_StandardObject, but I assume that the child class will override with a specific row class as the UserGateway class does when it specifies that it wants to use the UserObject row class.

The first thing in our API is the ability to create or update database rows. I've decided on a function called setFromArray() as a foundational function to allow this. The base implementation is performs these steps:

  1. Get hold of an instance of the row class
  2. Call the row's setFromArray() method
  3. Call the row's save() method

This is implemented as:


// (part of App_Model_Db_StandardGateway)

    public function setFromArray($data)
    {
        $row $this->_fetchRowOrCreate($data);
        $row->setFromArray($data);
        $row->save();

        return new $this->_rowClass($row->toArray());
    }

The tricky bit is getting the instance of the row class (an instance of Zend_Db_Table_Row) as we need to either create a new one or retrieve it from the database. Creating a new one is easy: $row = $table->createRow(). However, retrieving a row based on the $data array is more complicated. The simplest way I could think of was to retrieve the primary key info from the table and then call find() with the correct fields from $data array. To do this, I had to extend Zend_Db_Table_Abstract to implement a getPrimary() method like this:


class App_Db_Table_Standard extends Zend_Db_Table_Abstract
{
    protected $_rowClass 'App_Db_Table_Row_Standard';
   
    public function getPrimary()
    {
        return $this->_primary;
    }

}

Note that we also define a custom row class, App_Db_Table_Row_Standard which will allow us to perform operations on insert and delete.

Back to the App_Model_Db_StandardGateway class, we can now create _fetchRowOrCreate():


// (part of App_Model_Db_StandardGateway)

    /**
     * Either retrieve a row from the DB or create a new one
     *
     * @param array $data
     * @return App_Db_Table_Row_Standard
     */
    protected function _fetchRowOrCreate($data) 
    {
        $pk $this->_table->getPrimary();
        if (!is_array($pk)) {
            $pk = (array) $pk;
        }
        
        $pkData = array();
        foreach ($pk as $fieldName) {
            if (array_key_exists($fieldName$data)) {
                $pkData[] = $data[$fieldName];
            } elseif (!empty($pkData)) {
                throw new Exception("Could not find Primary Key part '$fieldName'");
            }
        }
        
        if (empty($pkData)) {
            $row $this->_table->createRow();
        } else {
            $rowset call_user_func_array(array($this->_table 'find'), $pkData);
            if ($rowset) {
                $row $rowset->current();
            } else {
                $row $this->_table->createRow();
            }
        }
        return $row;
    }

I'm nearly sure this can be improved, but it works for both a simple autoincrement primary key like id and also for compound primary keys of multiple columns which is nice. The method makes an assumption that if you have a compound key, then the $data array will have an entry for all fields that make up the primary key. If not, an exception is thrown. We also assume that if none of the fields that make up the primary key are present in $data, then you want to create a new record.

That covers creating and updating database records from our gateway class. Deletion is a simple proxy directly to the table class:


// (part of App_Model_Db_StandardGateway)

    public function deleteById($id)
    {
        $count $this->_table->delete('id = '. (int)$id);
        return (bool) $count;
    }

so let's look at fetching data. We need two methods in the App_Model_Db_StandardGateway class: _fetchRow() and fetchAll(). These are trivial to implement as we proxy directly to the table class and then all we need to do it turn each row returned into an instance of our StandardObject class:


// (part of App_Model_Db_StandardGateway)

    protected function _fetchRow($where$order null)
    {
        $row $this->_table->fetchRow($where$order);
        if ($row) {
            $row = new $this->_rowClass($row->toArray());
        }
        return $row;
    }

    public function fetchAll($orderBy=null)
    {
        $rows null;
        if(is_null($orderBy)) {
            throw new Exception('You must choose the order of the results');
        }
        $rowset $this->_table->fetchAll(null$orderBy);
        if($rowset) {
            $rows = array();
            foreach($rowset as $row) {
                $rows[] = new $this->_rowClass($row->toArray());
            }
        }
        return $rows;
    }

The StandardObject class' full name is App_Model_Db_StandardObject and is trivial:


class App_Model_Db_StandardObject
{
    function __construct($data = array()) {
        foreach($data as $fieldName=>$value) {
            $this->$fieldName $value;
        }
    }
}

Obviously, the intention is that the model specific version, UserObject in this case, is able to do provide model specific functionality. For testing, I simply added a name() method:


class UserObject extends App_Model_Db_StandardObject  
{
    public function name()
    {
        return $this->first_name ' ' $this->last_name;
    }
}

Clearly, I could provide additional methods as required and could overload the constructor if I wanted to do a more complicated mapping from the database row array to the value object representation.

Summary

This entry has turned out much longer than I expected! This is clearly my first attempt at putting the database inside the model and hiding it from the rest of the application. Having implemented this far, I'm not sure if using Zend_Db_Table is actually useful, so my next step will be to remove it and use SQL and a Zend_Db_Adapter instead. If it turns out well, I may blog about it :)

I'm also hoping to spark more debate on the actual implementation of how to do stuff like this. I know Keith Pope is working along similar lines for the example app in his upcoming book, so if you are interested in this approach, have a look at his tactics too as they may make more sense. Matthew Weier O'Phinney has also been doing something similar in his pastebin app which is worth looking at too, though he's halfway through refactoring.

Obviously, comments and suggested improvements are welcome!

Dynamic Tabs Subversion Respository

18th July 2006

With the help of Cliff, I've finally got a publically accessible subversion repository online at http://svn.akrabat.com!

The first project with actual code in it is my Dynamic Javascript Tabs. I've updated the trunk to include the change suggested by Samio so that the "activation" code is not part of the js file and is not part of the htm file.

Dynamic JavaScript Tabs v1.0.3

17th January 2006

Simon Hamilton contacted me about adding ids to each tab, so I did! I've also fixed the CSS error that Adam noticed and correctly updated the version number in the license block!

Update: I've created a separate page to hold the download link

Dynamic JavaScript Tabs v1.0.2

21st December 2005

I received a patch from Imobach González Sosa to allow the Dynamic JavaScript Tabs code to support more than one tab container set on a single page.

I've applied the patch and this is version 1.0.2!

Thanks Imobach!

Update: I've created a separate page to hold the download link

Bug in Dynamic JavaScript Tabs

24th April 2005

There's a bug in the Dynamic JavaScript Tabs code that I wrote. If you have "too many" tabs then they go onto a new line and look really ugly. It's a CSS issue that I haven't solved yet..

Anyone out there have any idea why I can't have 7 tabs? If you do, I'd appreciate a pointer!

Update
I've now worked out what it is and I'm being brain dead :) I set the width to 45em!

Dynamic JavaScript Tabs v1.0.1

28th March 2005

Using the Dynamic JavaScript Tabs in CMS Made Simple has shown up a small bug if you include the tabs.js file on a page that does not have an element with an id of "tabcontainer". Hence, I've released version 1.0.1 to fix this. No new features have been added.

Update: I've created a separate page to hold the download link

Dynamic JavaScript Tabs

22nd March 2005

This is some code that I wrote to create dynamic tabs. I use it for forms where I have a lot of fields and a good percentage are "advanced" or otherwise not important enough to have visible immediately. The CSS is based on ideas from Silverorange Labs and the JS code is mine. The code is released under the MIT license which basically means you can do what you like with it as long as you don't blame me!

Basic tutorial

It's not hard to use and I've included a sample html file in the zip archive. The basics are:

<div id="tab-container">
  <div class="tab-content">
      <h1 class="tab" title="title for page 1">Page 1</h1>
      <p>This is the content of tab 1</p>
  </div>
  <div class="tab-content">
      <h1 class="tab" title="title for page 2">Page 2</h1>
      <p>This is the content of tab 2</p>
  </div>
  <div class="tab-content">
      <h1 class="tab" title="title for page 3">Page 3</h1>
      <p>This is the content of tab 3</p>
  </div>
</div>
<script type="text/javascript" src="abs.js"></script>

i.e. you need a "tab-container" and then each tab is contained in a "tab-content" and we use a "tab" to sort out the title. The nice thing is that it degrades really nicely!

Update: I've created a separate page to hold the download link