Add memcache cache handler function
Add memcache cache handler function

There is an existing smarty memcache cache handler function out there,
however examples I've seen out there are incomplete in that they only
support evicting items from the cache using an exact key. However,
GitPHP takes advantage of some of the more advanced cache expiration
features of smarty, such as using cache groups and expiration based on
cache age.
Therefore this is an implementation done by me from scratch to support
these extra cache expiration mechanisms. Memcache doesn't inherently
support things like iterating over stored keys and getting the age of
a stored item. Therefore, I'm using a hack, where I maintain a mapping
table of stored keys and cache ages in a fixed key, and update that key
manually and use it for cache group or age expiration.
This also provides a memcache wrapper class to be able to transparently
support both the PECL Memcache and PECL Memcached extensions using the
same functions.

--- /dev/null
+++ b/include/cache/Memcache.class.php
@@ -1,1 +1,211 @@
-
+<?php
+/**
+ * GitPHP Memcache
+ *
+ * Memcache wrapper class to support both memcache extensions
+ *
+ * @author Christopher Han <xiphux@gmail.com>
+ * @copyright Copyright (c) 2010 Christopher Han
+ * @package GitPHP
+ * @subpackage Cache
+ */
+
+/**
+ * Memcache class
+ *
+ * @package GitPHP
+ * @subpackage Cache
+ */
+class GitPHP_Memcache
+{
+	/**
+	 * Memcache extension type constants
+	 */
+	const Memcache = 1;
+	const Memcached = 2;
+
+	/**
+	 * instance
+	 *
+	 * Stores the singleton instance
+	 *
+	 * @access protected
+	 * @static
+	 */
+	protected static $instance;
+
+	/**
+	 * GetInstance
+	 *
+	 * Returns the singleton instance
+	 *
+	 * @access public
+	 * @static
+	 * @return mixed instance of config class
+	 */
+	public static function GetInstance()
+	{
+		if (!self::$instance) {
+			self::$instance = new GitPHP_Memcache();
+		}
+		return self::$instance;
+	}
+
+	/**
+	 * Supported
+	 *
+	 * Returns whether memcache is supported by this PHP
+	 *
+	 * @access public
+	 * @static
+	 * @return true if memcache functions exist
+	 */
+	public static function Supported()
+	{
+		return (class_exists('Memcached') || class_exists('Memcache'));
+	}
+
+	/**
+	 * memcacheObj
+	 *
+	 * Memcache object
+	 *
+	 * @access protected
+	 */
+	protected $memcacheObj = null;
+
+	/**
+	 * memcacheType
+	 *
+	 * Memcache extension type
+	 *
+	 * @access protected
+	 */
+	protected $memcacheType = 0;
+
+	/**
+	 * __construct
+	 *
+	 * Constructor
+	 *
+	 * @access public
+	 * @return Memcache object
+	 */
+	public function __construct()
+	{
+		if (class_exists('Memcached')) {
+			$this->memcacheObj = new Memcached();
+			$this->memcacheType = GitPHP_Memcache::Memcached;
+		} else if (class_exists('Memcache')) {
+			$this->memcacheObj = new Memcache();
+			$this->memcacheType = GitPHP_Memcache::Memcache;
+		} else {
+			throw new GitPHP_MessageException('The Memcached or Memcache PHP extension is required for Memcache support', true, 500);
+		}
+	}
+
+	/**
+	 * AddServers
+	 *
+	 * Add servers to memcache
+	 *
+	 * @access public 
+	 * @param array $servers array of servers
+	 */
+	public function AddServers($servers)
+	{
+		if ((!$servers) || (!is_array($servers)) || (count($servers) < 1)) {
+			return;
+		}
+
+		if ($this->memcacheType == GitPHP_Memcache::Memcached) {
+			$this->memcacheObj->addServers($servers);
+		} else if ($this->memcacheType == GitPHP_Memcache::Memcache) {
+			foreach ($servers as $server) {
+				if (is_array($server)) {
+					$host = $server[0];
+					$port = 11211;
+					if (isset($server[1]))
+						$port = $server[1];
+					$weight = 1;
+					if (isset($server[2]))
+						$weight = $server[2];
+					$this->memcacheObj->addServer($host, $port, true, $weight);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Get
+	 *
+	 * Get an item from memcache
+	 *
+	 * @access public
+	 * @param string $key cache key
+	 * @return the cached object, or false
+	 */
+	public function Get($key = null)
+	{
+		return $this->memcacheObj->Get($key);
+	}
+
+	/**
+	 * Set
+	 *
+	 * Set an item in memcache
+	 *
+	 * @access public
+	 * @param string $key cache key
+	 * @param mixed $value value
+	 * @param int $expiration expiration time
+	 */
+	public function Set($key = null, $value = null, $expiration = 0)
+	{
+		if ($this->memcacheType == GitPHP_Memcache::Memcached)
+			return $this->memcacheObj->set($key, $value, $expiration);
+		else if ($this->memcacheType == GitPHP_Memcache::Memcache)
+			return $this->memcacheObj->set($key, $value, 0, $expiration);
+		return false;
+	}
+
+	/**
+	 * Delete
+	 *
+	 * Delete an item from the cache
+	 *
+	 * @access public
+	 * @param string $key cache key
+	 */
+	public function Delete($key = null)
+	{
+		return $this->memcacheObj->delete($key);
+	}
+
+	/**
+	 * Clear
+	 *
+	 * Clear the cache
+	 *
+	 * @access public
+	 */
+	public function Clear()
+	{
+		return $this->memcacheObj->flush();
+	}
+
+	/**
+	 * GetType
+	 *
+	 * Get the type of this memcache
+	 *
+	 * @access public
+	 * @return int memcache type
+	 */
+	public function GetType()
+	{
+		return $this->memcacheType;
+	}
+
+}
+

--- /dev/null
+++ b/include/cache/memcache_cache_handler.php
@@ -1,1 +1,109 @@
+<?php
+/**
+ * memcache_cache_handler
+ *
+ * Memcache smarty cache handler, with hacks to
+ * support cache groups and template ages
+ *
+ * @author Christopher Han <xiphux@gmail.com>
+ * @copyright (c) 2010 Christopher Han
+ * @package GitPHP
+ * @subpackage Cache
+ */
 
+define('MEMCACHE_OBJECT_MAP', 'memcache_objectmap');
+
+function memcache_cache_handler($action, &$smarty_obj, &$cache_content, $tpl_file = null, $cache_id = null, $compile_id = null, $exp_time = null)
+{
+	$memObj = GitPHP_Memcache::GetInstance();
+
+	$namespace = getenv('SERVER_NAME') . '_gitphp_';
+
+	$fullKey = $cache_id . '^' . $compile_id . '^' . $tpl_file;
+
+	switch ($action) {
+
+		case 'read':
+			$cache_content = $memObj->Get($namespace . $fullKey);
+			return true;
+			break;
+
+		case 'write':
+			/*
+			 * Keep a map of keys we have stored, and
+			 * their expiration times
+			 */
+			$map = $memObj->Get($namespace . MEMCACHE_OBJECT_MAP);
+			if (!(isset($map) && is_array($map)))
+				$map = array();
+
+			if (!isset($exp_time))
+				$exp_time = 0;
+			$map[$fullKey] = $exp_time;
+
+			$memObj->Set($namespace . $fullKey, $cache_content, $exp_time);
+			$memObj->Set($namespace . MEMCACHE_OBJECT_MAP, $map);
+			break;
+
+		case 'clear':
+
+			if (empty($cache_id) && empty($compile_id) && empty($tpl_file)) {
+				/*
+				 * Clear entire cache
+				 */
+				return $memObj->Clear();
+			}
+
+
+			$cachePrefix = '';
+			if (!empty($cache_id))
+				$cachePrefix = $cache_id;
+			if (!empty($compile_id))
+				$cachePrefix .= '^' . $compile_id;
+
+			$map = $memObj->Get($namespace . MEMCACHE_OBJECT_MAP);
+			if (isset($map) && is_array($map)) {
+				/*
+				 * Search through our stored map of keys
+				 */
+				foreach ($map as $key => $age) {
+					if (
+					    /*
+					     * If we have a prefix (group),
+					     * match any keys that start with
+					     * this group
+					     */
+					    (empty($cachePrefix) || (substr($key, 0, strlen($cachePrefix)) == $cachePrefix)) &&
+					    /*
+					     * If we have a template, match
+					     * any keys that end with this
+					     * template
+					     */
+					    (empty($tpl_file) || (substr($key, strlen($tpl_file) * -1) == $tpl_file)) &&
+					    /*
+					     * If we have an expiration time,
+					     * match any keys older than that
+					     */
+					    ((!isset($exp_time)) || ($age < $exp_time))
+					) {
+						$memObj->Delete($namespace . $key);
+						unset($map[$key]);
+					}
+				}
+
+				/*
+				 * Update the key map
+				 */
+				$memObj->Set($namespace . MEMCACHE_OBJECT_MAP, $map);
+			}
+			return true;
+			break;
+
+		default:
+			$smarty_obj->trigger_error('memcache_cache_handler: unknown action "' . $action . '"');
+			return false;
+			break;
+
+	}
+}
+

--- a/include/controller/ControllerBase.class.php
+++ b/include/controller/ControllerBase.class.php
@@ -76,6 +76,15 @@
 			if (GitPHP_Config::GetInstance()->HasKey('cachelifetime')) {
 				$this->tpl->cache_lifetime = GitPHP_Config::GetInstance()->GetValue('cachelifetime');
 			}
+
+			$servers = GitPHP_Config::GetInstance()->GetValue('memcache', null);
+			if (isset($servers) && is_array($servers) && (count($servers) > 0)) {
+				require_once(GITPHP_CACHEDIR . 'Memcache.class.php');
+				GitPHP_Memcache::GetInstance()->AddServers($servers);
+				require_once(GITPHP_CACHEDIR . 'memcache_cache_handler.php');
+				$this->tpl->cache_handler_func = 'memcache_cache_handler';
+			}
+
 		}
 
 		if (isset($_GET['p'])) {

file:a/index.php -> file:b/index.php
--- a/index.php
+++ b/index.php
@@ -20,6 +20,7 @@
 define('GITPHP_INCLUDEDIR', GITPHP_BASEDIR . 'include/');
 define('GITPHP_GITOBJECTDIR', GITPHP_INCLUDEDIR . 'git/');
 define('GITPHP_CONTROLLERDIR', GITPHP_INCLUDEDIR . 'controller/');
+define('GITPHP_CACHEDIR', GITPHP_INCLUDEDIR . 'cache/');
 define('GITPHP_LOCALEDIR', GITPHP_BASEDIR . 'locale/');
 
 include_once(GITPHP_INCLUDEDIR . 'version.php');

comments