<?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;
		}
	}
}

?>
