Image 01 Image 02

Using Zend_Registry as a Zend_Cache Backend

Posted on 29th August 2008 by Sameer
4

The Zend Framework provides a Zend_Cache which can be plugged into with various backends such as SQLite, Memcached, APC, and so on. Separately, it also provides Zend_Registry which is a “a container for storing objects and values in the application space”. The Zend_Registry is not a cache as its contents are created and used only by the currently executing script.

So, why would you want to use the Registry as a cache when it does not cache anything between page loads? The answer is to provide a transition point for caching additional data in other Zend_Cache backends.

For example, every time a Zend_Db_Table is instatiated it runs a DESCRIBE TABLE query which is a surprisingly expensive query (or at least it was surprising to me). If you are using the MVC model, you can end up running this query dozens of times on one page. So to speed things up you should cache the results of the DESCRIBE TABLE query. You will end up improving performance whether you save the results in the Registry or (even better) in an appropriate Zend_Cache backend.

However, at the moment you have not configured your Memcached daemon so you instead decide to use the Zend_Registry. But Zend_Registry does not follow the same syntax as Zend_Cache. So, when you do finally set up Memcached you will have to go back, edit your code to follow the Zend_Cache sytanx, and then test your cache. It’s better to instead use Zend_Registry as a backend to Zend_Cache which will make it utterly simple to change the cache backend to Memcached at a later date.

And, if you ever are not sure whether its worthwhile (or logically appropriate) to cache data in a persistant cache, having Zend_Registry as a backend allows you to experiment quickly and then later make the change to a different backend.

Note: Because of the short life of Zend_Registry as a cache, I did not implement expiration times nor tags.

<?php

/**
* @see Zend_Cache_Backend_Interface
*/
require_once 'Zend/Cache/Backend/Interface.php';

/**
* @see Zend_Cache_Backend
*/
require_once 'Zend/Cache/Backend.php';

/**
* @package    Zend_Cache
* @subpackage Zend_Cache_Backend
* @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
* @license    http://framework.zend.com/license/new-bsd     New BSD License
*/
class Zend_Cache_Backend_Registry extends Zend_Cache_Backend implements Zend_Cache_Backend_Interface
{

// array name for the registry entries
protected static $_registeryCacheArrayName = "registryCacheArray";

// a static instance of the registry
protected static $_registryCacheArray;

/**
* Constructor
*
* @param  array $options associative array of options
* @throws Zend_Cache_Exception
* @return void
*/
public function __construct($options = array())
{
    parent::__construct($options);
    $registry = Zend_Registry::getInstance();
    if (!isset($registry[self::$_registeryCacheArrayName])) $registry[self::$_registeryCacheArrayName] = array();
    // copy by reference
    self::$_registryCacheArray = $registry[self::$_registeryCacheArrayName];
}

/**
* Test if a cache is available for the given id and (if yes) return it (false else)
*
*
* @param  string  $id                     cache id
* @param  boolean $doNotTestCacheValidity if set to true, the cache validity won't be tested
* @return string cached datas (or false)
*/
public function load($id, $doNotTestCacheValidity = false)
{
    if (!isset(self::$_registryCacheArray[$id])) return false;
    else return self::$_registryCacheArray[$id];
}

/**
* Test if a cache is available or not (for the given id)
*
* @param  string $id cache id
* @return boolean true if exists false if not
*/
public function test($id)
{
    return isset(self::$_registryCacheArray[$id]);
}

/**
* Save some string datas into a cache record
*
* Note : $data is always "string" (serialization is done by the
* core not by the backend)
*

* @param string $data datas to cache
* @param string $id cache id
* @param array $tags array of strings, the cache record will be tagged by each string entry
* @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime)
* @return boolean true if no problem
*/
public function save($data, $id, $tags = array(), $specificLifetime = false)
{
    self::$_registryCacheArray[$id] = $data;
    if (count($tags) > 0) {
        $this->_log("Zend_Cache_Backend_Registry::save() : tags are unsupported by the registry backend");

    }
    return true;
}

/**
* Remove a cache record
*
* @param  string $id cache id
* @return boolean true if no problem
*/
public function remove($id)
{
    unset(self::$_registryCacheArray[$id]);
}

/**
* Clean some cache records
*
* Available modes are :
* 'all' (default)  => remove all cache entries ($tags is not used)
* 'old'            => remove too old cache entries ($tags is not used)
* 'matchingTag'    => remove cache entries matching all given tags
*                     ($tags can be an array of strings or a single string)
* 'notMatchingTag' => remove cache entries not matching one of the given tags
*                     ($tags can be an array of strings or a single string)
*
* @param  string $mode clean mode
* @param  array  $tags array of tags
* @return boolean true if no problem
*/
public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
{
    if ($mode==Zend_Cache::CLEANING_MODE_ALL) {
        self::$_registryCacheArray = array();
    }
    if ($mode==Zend_Cache::CLEANING_MODE_OLD) {
        $this->_log("Zend_Cache_Backend_registry::clean() : CLEANING_MODE_OLD is unsupported by the registry backend");

    }
    if ($mode==Zend_Cache::CLEANING_MODE_MATCHING_TAG) {
        $this->_log("Zend_Cache_Backend_registry::clean() : tags are unsupported by the registry backend");

    }
    if ($mode==Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG) {
        $this->_log("Zend_Cache_Backend_registry::clean() : tags are unsupported by the registry backend");

    }
}

/**
* Return true if the automatic cleaning is available for the backend
*
* @return boolean
*/
public function isAutomaticCleaningAvailable()
{
    return false;
}

}

And to use Zend_Cache to cache DESCRIBE TABLE queries:

class MyZend_Db_Table extends Zend_Db_Table {

protected static $metadataCache;

public function __construct($config = array())
{
if (!self::$metadataCache) {
self::$metadataCache = Zend_Cache::factory('Core', 'Registry', array('automatic_serialization' => true));
}

if (!isset($config[self::METADATA_CACHE])) $config[self::METADATA_CACHE] = self::$metadataCache;
parent::__construct($config);

}
}

Just remember that your own Db Tables should then extend MyZend_Db_Table and not Zend_Db_Table.



4
Responses to.. Using Zend_Registry as a Zend_Cache Backend

1
Sagattarii posted on November 22nd 2008

Hi,

I found your page via google searching for “describe” and “zend framework”. I noticed that ZF performs a surprisingly expensive “describe” query, just as you.
Two points from me:
1. I don’t think your solution is faster than the mysql query cache, which should be always turned on. With the query cache, two describe queries shouldn’t be that longer than one. But your extension of Zend_Db_Table is a nice idea.
2. Just change your db adapter from PDO_MYSQL to mysqli and ZF stops doing “describe” queries. It worked for me :)

Btw I think it is very anoying for most users that you have to register at your blog to post a comment. Turn this off and more users will post a comment :)



2
Sameer posted on November 23rd 2008

Hi Sagattarii,

I will probably allow unregistered users to comment within a few days. Just got to make sure I have the anti-spam plugin working effectively.

As for your points… I don’t believe the query cache works on the DESCRIBE statement and even if it did there are often times when using the query cache is a bad idea (if your app has a good of writes, setting the query cache to automatic is usually a bad idea).

I like point #2 though. Are you sure though? It seems the reason ZF uses describe is to fill its metadata about the table. How would using PDO make that easier without DESCRIBE?



3
roonaan posted on December 24th 2008

Hi,

An interesting approach. Similarly a Zend_Cache_Backend_Session could be implemented fairly easily based on your excellent code. This allows for caching of user details, and expensive user related queries using just a Zend_Session_Namespace as the backend for your cache.

[code]
data)) {
self::$_namespace->data = array();
}
}
/**
* Test if a cache is available for the given id and (if yes) return it (false else)
*
*
* @param string $id cache id
* @param boolean $doNotTestCacheValidity if set to true, the cache validity won’t be tested
* @return string cached datas (or false)
*/

public function load($id, $doNotTestCacheValidity = false){

if (!isset(self::$_namespace->data[$id])) {
return false;
}

return self::$_namespace->data[$id];
}

/**
* Test if a cache is available or not (for the given id)
*
* @param string $id cache id
* @return boolean true if exists false if not
*/
public function test($id) {
return isset(self::$_namespace->data[$id]);
}

/**
* Save some string datas into a cache record
*
* Note : $data is always “string” (serialization is done by the
* core not by the backend)
*
* @param string $data datas to cache
* @param string $id cache id
* @param array $tags array of strings, the cache record will be tagged by each string entry
* @param int $specificLifetime if != false, set a specific lifetime for this cache record (null => infinite lifetime)
* @return boolean true if no problem
*/
public function save($data, $id, $tags = array(), $specificLifetime = false) {
self::$_namespace->data[$id] = $data;
if (count($tags) > 0) {
$this->_log(”Zend_Cache_Backend_Registry::save() : tags are unsupported by the session backend”);
}
return true;
}

/**
* Remove a cache record
*
* @param string $id cache id
* @return boolean true if no problem
*/
public function remove($id) {
unset(self::$_namespace->data[$id]);
return true;
}

/**
* Clean some cache records
*
* Available modes are :
* ‘all’ (default) => remove all cache entries ($tags is not used)
* ‘old’ => remove too old cache entries ($tags is not used)
* ‘matchingTag’ => remove cache entries matching all given tags
* ($tags can be an array of strings or a single string)
* ‘notMatchingTag’ => remove cache entries not matching one of the given tags
* ($tags can be an array of strings or a single string)
*
* @param string $mode clean mode
* @param array $tags array of tags
* @return boolean true if no problem
*/
public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array()) {
if ($mode==Zend_Cache::CLEANING_MODE_ALL) {
self::$rnamespace->data = array();
}
if ($mode==Zend_Cache::CLEANING_MODE_OLD) {
$this->_log(”Zend_Cache_Backend_registry::clean() : CLEANING_MODE_OLD is unsupported by the session backend”);
}
if ($mode==Zend_Cache::CLEANING_MODE_MATCHING_TAG) {
$this->_log(”Zend_Cache_Backend_registry::clean() : tags are unsupported by the session backend”);
}
if ($mode==Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG) {
$this->_log(”Zend_Cache_Backend_registry::clean() : tags are unsupported by the session backend”);
}
}

/**
* Return true if the automatic cleaning is available for the backend
*
* @return boolean
*/
public function isAutomaticCleaningAvailable() {
return false;
}
}
[/code]



4

[...] The debug bar already helped me speed up some of my Zend Framework apps. I noticed some describe queries in my app, which apparently can be cached. [...]



Leave a reply...