[Patch] Cache handler overhaul

Trevor Burnham <[email protected]> Sun, 15 Oct 2006 17:54:24 -0600
Newsgroups gmane.comp.php.smarty.devel
Message-ID <[email protected]>
Hi,

First-time contributor, so let me know if I'm doing anything wrong.

I've done some coding to change the way Smarty works with cache   
handlers.  Right now, there are several flaws with using an   
alternative cache handler (like the MySQL or eAccelerator examples   
that have been floating around):
* Much of the data that's cached in Smarty's cache files (such as   
expiration data) isn't sent to the cache handler
* is_cached only checks if Smarty has a cache file, regardless of   
what the cache handler is
* All of the cache handler code I've seen doesn't, well, actually   
work with the current version of Smarty

I've done a rewrite so that these problems are fixed, cache handlers   
can respond to 'check' calls for is_cached (in addition to 'read',   
'write' and 'clear' calls), and so that multiple cache handlers can   
be put in an array to be called sequentially.  This would be useful   
if you had a site with a huge amount of cached data in Smarty and  
you  wanted to, say, try a memory cache first, then a hard disk cache  
if  the memory cache failed.  I've written a Memcached cache handler  
that  demonstrates this.

The code could use some cleaning up, but it seems to work reliable  
and doesn't break any existing functionality (unless, possibly, you  
were already using an alternative cache handler; you will have to  
modify it).  .diff is attached, as is the Memcached handler.

Trevor Burnham

-- 
Smarty Development Mailing List (http://smarty.php.net/)
To unsubscribe, visit: http://www.php.net/unsub.php
SmartyMemcached.class.php (text/php, 6.5 KB)
<?php

/**
 * Memcached cache handler class for Smarty.
 *
 * It should result in a substantial increase in Smarty's performance, since
 * cached data will be read from memory instead of the hard disk when possible.
 * It's recommended, however, that you use this in conjunction with Smarty's
 * disk cache (or some other reliable cache), since data can get pushed out of
 * your memory cache if it fills up.
 *
 * Use this cache handler only if Memcached is installed and running, AND if
 * the Memcache PHP extension from PECL is installed.  Otherwise, no go.  For
 * speed's sake, no error-checking is performed in the cache handler function,
 * so follow these instructions carefully.
 *
 * This cache handler does not yet implement timestamp, expires, and other
 * meta-data that is stored by the Smarty file cache handler.  But it could.
 *
 * Warning:<br />
 * Due to the limitations of Memcached, clear_all_cache is not implemented.  If
 * you might want to be able to clear your entire cache, then don't use this
 * handler.  However, you can set it up as your first cache and Smarty's disk
 * cache as your second cache, and check "$smarty->is_cached" before displaying
 * any cached data; is_cached will check to see if an item is in the disk cache,
 * which IS affected by clear_all.  In that way, you can bypass the Memcached
 * cache.
 *
 * Usage:<br />
 * Install this file in the Smarty/plugins directory as
 * function.memcached_cache_handler.php.  Then use the following code:
 * 
 * include('Your_Smarty_dir/Smarty.class.php');<br />
 * include('Your_Smarty_dir/plugins/SmartyMemcached.class.php');
 *
 * $smarty=newSmarty();<br />
 * // Add SmartyMemcached configuration, if needed--see below
 * $smarty->cache_handler_func=
 *	 array('SmartyMemcached::cache_handler', 'Smarty::file_cache_handler');<br />
 * $smarty->caching = true;<br />
 * $smarty->display('index.tpl');
 *
 * Additional configuration:<br />
 * By default, the cache handler function assumes that you're running Memcached
 * on port 11211 of localhost.  However, you can use anything you want as your
 * Memcached server by doing either of the following:
 *
 * - Call memcached_cache_handler_set_server with the server name (as a string)
 *   and port (as an int) that's running your Memcached instance.
 * - Call memcached_cache_handler_set_object with a PECL memcache object that
 *   you've already connected (or pconnected) to.
 *
 * If the Memcached instance is shared with any other apps (or, worse, other
 * users), then use the memcached_cache_handler_set_server function and supply
 * the optional prefix parameter.  Otherwise, you might accidentally retrieve--
 * or overwrite--someone else's cached data!
 *
 * Credits:<br />
 * This code is based heavily upon the MySQL cache handler example from the
 * official Smarty manual.
 *
 * @author Trevor Burnham
 * @version 1.0
 * @package Smarty
 * @subpackage plugins
 */

class SmartyMemcached {
	static $memcache_instance = null;
	static $key_prefix = '';
	
	/**
	 * Connect to a Memcached server, given a name and port.
	 *
	 * @paramv string server	e.g. 'memcached-server.com' or '184.13.134.0'.
	 * @param integer port		11211 is the most common Memcached port.
	 * @return					True on success, false otherwise.
	 */
	static function setServer($server = 'localhost', $port = '11211')
	{
		SmartyMemcached::$memcache_instance = new Memcache();
		return SmartyMemcached::$memcache_instance->pconnect($server, $port);
	}
	
	/**
	 * Provide a Memcache object to use for the cache.
	 *
	 * Optionally, a key prefix can be provided; this is STRONGLY recommended if
	 * the Memcached instance is shared with any other applications or users.  If
	 * no prefix is supplied, keys are in the format "Smarty:$tpl_file:$cache_id";
	 * if a prefix is supplied, the format is "$prefix:Smarty:$tpl_file:$cache_id".
	 * A good prefix would be your site's domain name, or an MD5 hash of your home
	 * page--anything that no one else is likely to use.
	 *
	 * (:$compile_id is also appended to the key name if provided.  In a pinch,
	 * it could be used as a unique identifier.)
	 *
	 * @param Memcache memcache
	 * @param string prefix
	 */
	static function setObject($memcache, $prefix)
	{
		SmartyMemcached::$memcache_instance = $memcache;
		SmartyMemcached::$key_prefix = $prefix;
	}
	
	/**
	 * The cache handler function called by Smarty.
	 *
	 * A read tries Memcached first, then Smarty's file cache.  A write stores the
	 * data in both Memcached and the file cache, and a clear removes the data from
	 * both caches.
	 *
	 * @param string action			'read', 'write', or 'clear'.
	 * @param Smarty smarty_obj
	 * @param string cache_content	A dummy variable should be used for 'clear's.
	 *								Used to supply data for 'write' and to return
	 *								data (by reference) on 'read'.
	 * @param string tpl_file
	 * @param string cache_id
	 * @param string compile_id
	 * @param null exp_time
	 * @return boolean				True on success, false otherwise.
	 */
	
	static function cache_handler($action, &$smarty_obj, &$cache_content,
	  $tpl_file=null, $cache_id=null, $compile_id=null, $exp_time=null)
	{
		if (SmartyMemcached::$memcache_instance == null)
			SmartyMemcached::setServer();
		$memcached_key = $tpl_file.':'.$cache_id;
		if (SmartyMemcached::$key_prefix != '')
			$memcached_key = SmartyMemcached::$key_prefix.':'.$memcached_key;
		if ($compile_id != null)
			$memcached_key .= ':'.$compile_id;
		
		switch ($action) {
			case 'read':
				/* Get data from Memcached, if possible. */
				$cache_content = SmartyMemcached::$memcache_instance->get($memcached_key);
				// echo "Read Memcached cache content: $cache_content<br />";
				if ($cache_content == false) {
					return false;
				} else {
					return true;
				}
			case 'write':
				/* Put data in Memcached. */
				// echo "Wrote to Memcached: $cache_content<br />";
				return SmartyMemcached::$memcache_instance->set($memcached_key, $cache_content);
			case 'check':
				if ($memcache_instance->get($memcached_key) == false)
					return 'defer'; // The data may still exist in another cache.
				else
					return true;
			case 'check_final':
				if (SmartyMemcached::$memcache_instance->get($memcached_key) == false)
					return false;
				else
					return true;
			case'clear':
				/* Remove the data corresponding to the given key from Memcached. */
				return SmartyMemcached::$memcache_instance->delete($memcached_key);
			default:
				/* Wow, someone messed up. */
				$smarty_obj->_trigger_error_msg(
							  "cache_handler (Memcached):unknownaction\"$action\"");
				return false;
		}
	}
}

?>