Caching Zend_Db_Table Models

18.05.2009 0

(Dieser Artikel ist ausnahmsweise in Englisch)

Have you ever felt the need to cache Databaseresults ? Of course you have. Do you also use Zend_Db_Table for the less complex queries ? Then this may be interesting for you.

Of course, these simple queries would easily fit in MySQL (or any other DB) query cache. But maybe you want to save this for your complex queries building Tmp-Tables in MEMORY. Or for whatever reason, here is a solution.

You find the full classes on my svn.

The basic idea is from Pascal Opitz with contributions from Robert Kummer and Sascha-Oliver Prolic. Their version is great but does not work with Zend_Db_Table.

Let’s start with extending Zend_Db_Table_Abstract:


abstract class Cwd_Db_Table extends Zend_Db_Table_Abstract {
	/**
	 * @var Cwd_Db_Cache
	 */
	public $cache;

	protected $_rowClass ='Cwd_Db_Table_Row';

	public function __construct($config=array()) {
		parent::__construct($config);
		/**
		 * @var Cwd_Db_Cache
		 */
		$this->cache = new Cwd_Db_Cache($this);
	}
}

We just add the cache to it and set the protected var $_rowClass, this is needed because we want the results to be of our own implementation of Zend_Db_Table_Row_Abstract.

Our implementation looks like this:


class Cwd_Db_Table_Row extends Zend_Db_Table_Row_Abstract {
	/**
	 * @var Cwd_Db_Cache
	 */
	public $cache;

	public function __construct($config=array()) {
		parent::__construct($config);

		$this->cache = new Cwd_Db_Cache($this);
	}

	public function setTable(Zend_Db_Table_Abstract $table = null)
    {
        if ($table == null) {
            $this->_table = null;
            $this->_connected = false;
            return false;
        }

        $tableClass = get_class($table);

        /**
        // we lost $this->_tableClass, so this would throw always an exception
		// we have to ignore this for now.

        if (! $table instanceof $this->_tableClass) {
            require_once 'Zend/Db/Table/Row/Exception.php';
            throw new Zend_Db_Table_Row_Exception("The specified Table is of class $tableClass, expecting class to be instance of $this->_tableClass");
        }
		*/

        $this->_table = $table;
        $this->_tableClass = $tableClass;

        $info = $this->_table->info();

        if ($info['cols'] != array_keys($this->_data)) {
            require_once 'Zend/Db/Table/Row/Exception.php';
            throw new Zend_Db_Table_Row_Exception('The specified Table does not have the same columns as the Row');
        }

        if (! array_intersect((array) $this->_primary, $info['primary']) == (array) $this->_primary) {

            require_once 'Zend/Db/Table/Row/Exception.php';
            throw new Zend_Db_Table_Row_Exception("The specified Table '$tableClass' does not have the same primary key as the Row");
        }

        return true;
    }	

    /**
     * Set the table
     * this is necessary if the row lies in cache
     *
     * also we aresetting automatically a cache suffix
     *
     * @param $table Zend_Db_Table_Abstract A instance of a model.
     */
    public function prep($table){
    	$this->setTable($table);
        $table=$this->getTable()->info();
        $tablename=$table['name'];
        $primary=$table['primary'][1];

        $this->cache->setCacheSuffix($tablename.'__'.$this->$primary.'__');
    }
}

Ok, what did we do. We have our cache in it, and are overwriting the setTable method.  If the row is stored in the cache, we lose _tableClass, and the only way to set it right again is to use the setTable method, but unfortunatly this method is checking if the table object we give to it is the same as the object in _tableClass … you see the problem. So we have to overwrite this only to comment this part out.

At the bottom you see a new prep method. This method calls the setTable method and also sets a cache suffix. This is needed becouse the Zend_Cache_Frontend_Class generates the id only by method name and parameters, but not on contents of the object. So if you have a loop and for example use findParentRow() in it, you would get the same row for every  Query from the cache. This is not what we want. So we autoextend the cache suffix by the primary key column with the value of the primary key. (in the current version, there is no check if the primary key has more than one field. If you need this feel free to overwrite/extend my class)

To make use of these classes you just have to build your models by extending Cwd_Db_Table instead of Zend_Db_Table_Abstract.

If you have done so, you can use it like in the following example:



    	$m_event = new EventModel();
    	$m_act = new ActModel();
    	$events = $m_event->cache->fetchAll(array("status='1'","deleted='0'"),'createdate desc',10,0);
    	echo "<ul>";
    	foreach($events as $event){
    		echo "<li>".$event->event_id."</li>";
			$event->prep($m_event);

    		$acts = $event->cache->findDependentRowset('ActModel');
    		echo "<ul>";
    		foreach($acts as $act){
    			$act->prep($m_act);
    			$foo = $act->cache->findParentRow('EventModel');
    			echo "<li>".$act->act_id." (".$foo->event_id.")</li>";
    		}
    		echo "</ul>";
    	}
    	echo "</ul>";

Easy, isn’t it ? The caching is flexible, so it doesn’t matter if all rows are in the cache. All combinations are working. To bypass the cache you can use the model directly without the ->cache->.

The cache class is using Zend_Config for some parameters, for reference here are the values:

caching.cache_by_default	= true
caching.default_cache_id_prefix	= default_
caching.lifetime			= 300
caching.backendName			= Memcached
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • MySpace
  • Reddit
  • Slashdot
  • Technorati
  • MisterWong.DE
  • StumbleUpon

Trackback-URL

No comments

Leave a reply