Notes on Zend_Cache
Recently I needed to speed up a legacy project that makes a lot of database calls to generate each page. After profiling, I discovered that 90% of the database calls returned data that rarely changed, so decided to cache these calls. One of the nice things about Zend_Framework is that its use-at-will philosophy means that you can use any given component with minimal dependencies on the rest of the framework code.
In my case, I wanted to use Zend_Cache, so I needed Zend/Cache/*, Zend/Cache.php, Zend/Loader/*, Zend/Loader.php and Zend/Exception.php and didn't bother with any other part of the framework.
The application I'm speeding up is completely procedural with lots of include files and no virtually no classes anywhere other than in the lib/ directory! I wanted to minimise the disruption to the current code and so it seemed that a simple static class that provided a set of proxy functions to an underlying Zend_Cache object would be easiest. I also provided a mechanism to turn off the cache using a simple boolean that could be set when initialising the class.
The class is called TheCache:
class TheCache { /** * @var boolean */ static protected $_enabled = false; /** * @var Zend_Cache_Core */ static protected $_cache; static function init($enabled, $dir, $lifetime=7200) { self::$_enabled = $enabled; if(self::$_enabled) { require_once 'Zend/Cache.php'; $frontendOptions = array( 'lifetime' => $lifetime, 'automatic_serialization' => true, ); $backendOptions = array( 'cache_dir' => $dir, 'file_name_prefix' => 'thecache', 'hashed_directory_level' => 2, ); self::$_cache = Zend_Cache::factory('Core', 'File', $frontendOptions, $backendOptions); } } static function getInstance() { if(self::$_enabled == false) { return false; } return self::$_cache; } static function load($keyName) { if(self::$_enabled == false) { return false; } return self::$_cache->load($keyName); } static function save($keyName, $dataToStore) { if(self::$_enabled == false) { return true; } return self::$_cache->save($dataToStore, $keyName); } static function clean() { if(self::$_enabled == false) { return; } self::$_cache->clean(); } }
The init() function is used to set up the cache to use files stored in the supplied directory. The only configuration is to choose whether the cache is enabled and the lifetime of all objects stored in it. Note that this is where we get specific to the problem in hand. In this specific case, I didn't need different lifetimes for each item stored in the cache, which nicely simplified everything.
Let's look at how it is used. First we initialise the cache in an include file that happens to be included for every request:
$cacheEnabled = (bool)getenv('THE_CACHE_ENABLED') ? getenv('THE_CACHE_ENABLED') : false; TheCache::init($cacheEnabled, TMP_DIR.'/the-cache/');
I use an environment variable set using SetEnv within my Apache virtual hosts to determine if the cache should be enabled or not, so we use getenv() to retrieve the value and then call TheCache::init(). The use of THE_CACHE_ENABLED allows me to disable the cache on my development machine, and have it enabled on my live server without having any code changes. Obviously, TMP_DIR is defined previously.
Now that we have initialised the cache, we can use it. Within this application, we generally use the ADODB database classes to generate arrays of data from the database along these lines:
$sql = 'SELECT x,y FROM z WHERE a=b'; $rs = $db->Execute($sql); $data = $rs->GetArray();
To cache this information, I changed the code to look like this:
$keyName = 'data-z-a-b'; // unique name describing this data set $data = TheCache::load($keyName) if($data === false) { $sql = 'SELECT x,y FROM z WHERE a=b'; $rs = $db->Execute($sql); $data = $rs->GetArray(); TheCache::save($keyName, $data); }
Firstly we invent a unique name for the dataset and assign to $keyName and then load the data from the cache object using the load() function. If the data is cached, then we are done. If not, we perform the SQL query to get the data and then store it into the cache using save(). Rinse and repeat for each operation whose results you want to cache.
And that's all there is to it.

July 11th, 2008 at 09:30 #
"After profiling, I discovered that 90% of the database calls returned data that rarely changed"
It would be nice if in the future you show us how you do this.
Thank you, greate article, as always :)
July 11th, 2008 at 10:32 #
Is there any reason to not declare init()-method as static?
July 11th, 2008 at 11:27 #
svenwin,
Carelessness on my part when formatting for posting here! I've updated.
Thanks,
Rob…
July 13th, 2008 at 13:03 #
If you use AdoDB, why don't you just use the built-in caching possibilities?
July 14th, 2008 at 08:18 #
Jeroen,
Although I only showed the database related items here, we also cached some non-db related processes.
Regards,
Rob…
July 14th, 2008 at 08:19 #
iongion,
I have a Zend Studio 5.5 license which has a profiler built in which is very useful at times :)
Rob…
July 15th, 2008 at 15:16 #
As for me I do not like Zend Studio etc. I love Eclipse!
July 15th, 2008 at 18:54 #
Snowcore
from what i discovered, the new zend studio is eclipse with a zend php plugin.
July 16th, 2008 at 13:13 #
@iongion: I think you're right about that, I believe it is a version of the PDT with more features.
I also have a post on my site (http://www.chrisabernethy.com/zend-framework-route-benchmarks/) that talks about profiling using eclipse, PDT, xdebug and kcachegrind.
July 29th, 2008 at 14:34 #
Hi Rob, I was wondering, is it possible to use Zend_Cache to convert my dynamic generated pages into static HTML pages? For example,
http://www.example.com/update/show/id/3
would map to the following page:
http://www.example.com/update/show/3.html
At this time, I'm interested in page caching.
Thanks in advance,
-Conrad
August 4th, 2008 at 18:24 #
Conrad,
I would think you could do something with a custom route and Zend_Cache.
Regards,
Rob…