<?php
require_once LIB_DIR . '/cache/ActractCache.php';
class CacheNotConnectedException extends RuntimeException
{
public function __construct($message)
{
parent::__construct($message);
$log = Logger::getLogger('exception');
$log->fatal('cache not connected: ' . $message);
}
}
class NoCacheServersDefinedException extends RuntimeException
{
public function __construct($message)
{
parent::__construct('no defined cache servers: ' . $message);
$log = Logger::getLogger('exception');
$log->fatal('no defined cache servers: ' . $message);
}
}
/**
* memcache impl
*
* @property NetworkCache $_instance the singleton ref to
* @property MemCache $_cache the actual memcache object ref
* @property boolean $_connected true if we have a good memcache connection
* @property integer $_key the memcache key (crc32 hash)
* @property string $_origKey the memcache key in human format
* @property string $_nameSpace the current namespace
* @property array $_savePage helper to keep state between createing pages namespace keys, and
*
* @author Cory
* @package lib
*/
class NetworkCache extends AbstractCache
{
private static $_instance;
protected $_cache;
/**
* connect to the memcache servers. Actually this will setup the connection "pool"
*/
protected function __construct()
{
$this->_log = Logger::getLogger('cache');
$this->_connected = false;
// else, create the new connection
if (LIB_CACHE_ENABLE)
$this->startCache();
else
return $this->_log->warn('NetworkCache is not enabled.');
// memcache addServer is broken, always returns false.. oh well, maybe someday it will work...
$this->_connected = true;
}
/**
* called on add server failure
* @param string $host host add failure host
* @param string $port tcp port
*/
public static function MemCacheFailure($host, $port)
{
$log = Logger::getLogger('cache');
$log->fatal("Memcache server connection error to host: $host:$port");
}
/**
* called by constructor to connect to cache servers
* @return Memcache reference to Memcache object
*/
protected function startCache()
{
if (isset($this->_cache))
return;
// else, create the new connection
$cache = new Memcache();
$serverCount = 0;
for ($S = 1; $S <= 10; $S++)
{
$server = 'MEMCACHE_SERVER'.$S;
if (defined($server))
{
$cache->addServer(constant($server), 11211, true, 100, 1, 15, true, 'NetworkCache::MemCacheFailure');
$serverCount++;
}
}
if (LIB_CACHE_ENABLE && $serverCount == 0)
throw new NoCacheServersDefinedException();
$this->_cache = $cache;
}
/**
* get a singleton reference to the mem cache object. Be sure to set
* a namespace before using the cache. You can specify a namespace here
* of use the setNameSpace method.
*
* @see setNameSpace
* @param $nameSpace the name of the namespace to connect to, only
* required for nameSpace cached items;
* @return NetworkCache
*/
public static function getInstance($nameSpace = false)
{
if (isset(self::$_instance[$nameSpace]))
return self::$_instance[$nameSpace];
$ref = new NetworkCache();
$ref->setNameSpace($nameSpace);
return self::$_instance[$nameSpace] = $ref;
}
/**
* test if the cache is connected to the server(s). This is difficult to do
* when using the "pool" since no connection is attempted until the fist
* get or put request. At that point it will either connect or fail.
*
* It is also difficult to test all servers since some keys will be mapped
* to different servers and while some tests may work, tests with other keys
* mapped to different servers may fail. For these reasons out of band
* server testing should determine what servers are up, and only up servers
* should be added to the pool to be used. This creates the additional
* problem of key remapping if a server is added to the pool altering the
* server mapping. A server key mapping strategy needs to be employed for
* the best results.
*
* has this ever really worked?
*
* @return boolean true if this object is connected to MemCache, false otherwise
* currently this will always return true.
*/
public function isConnected()
{
return $this->_connected;
}
/**
* get the cached value for the current key, see createKey to define the
* key to get the cache value for.
*
* @see createKey for how to define the cache key
* @see createNameSpaceKey for how to define name space cache keys
*
* @param boolean $throws set to boolean true if we should throw sql exceptions if the
* data was not found. This makes it easy to use a try catch block to get
* the data from cache, and load it if cache retrevial failed
* @param integer $flags
* @throws BSException if memcache is not connected
* @throws RuntimeException if the cached data was not found
* @return mixed the thing that was cached in memcache
*/
public function get($throws = false, &$flags = false)
{
if (!LIB_CACHE_ENABLE)
return $this->_log->warn('Cache is not enabled LIB_CACHE_ENABLE = false');
$this->_log->logTrace('get item');
// don't even attempt if memcache is not connectecd
if (!$this->isConnected())
return $this->_log->warn('memcache is not connected, get failed!');
// if someone passed in a key name to get, then try to work with that
// Cory added this for Eugene
if (is_string($throws) && strlen($throws) > 3)
{
$this->createKey($throws);
$this->_log->error("!!!! ID10T error, you called get() with a keyname, use createKey() instead.");
}
// get the data from the cache pool
$result = $this->_cache->get($this->_key);
//$this->_log->logTrace('get ' . $this->_key . ' item was: ' . print_r($result, true));
if ($result == false && $throws === true)
throw new RuntimeException("key: {$this->_origKey} was not found");
else if ($result == false)
return $this->_log->debug("key: {$this->_origKey} was not found in cache");
else
$this->_log->debug("key: {$this->_origKey} is used from cache");
return $result;
}
/**
* set the cached value for the current key, see createKey to define the
* key to get the cache value for. If the key exists, it is overwritten
*
* @see createKey for how to define the cache key
* @see createNameSpaceKey for how to define name space cache keys
*
* @param mixed $value the data to cache
* @param integer $expire the number of seconds the item expires, default is 1 hour
* @param integer $flags any flags to set during the memcache set, default is false
* @param boolean $throwOnFail set to true to throw excpetion on cache set fail
* @throws BSException if setting the key fails
* @throws BSException if memcache is not connected
* @return true on success
*/
public function set($value, $expire = 3600, &$flags = false, $throwOnFail = false)
{
if (!LIB_CACHE_ENABLE)
return $this->_log->warn('Cache is not enabled.');
$this->_log->logTrace('set ' . $this->_key . ' for ' . $expire . " sec");
// don't even attempt if memcache is not connectecd
if (!$this->isConnected())
return $this->_log->warn('memcache is not connected, set failed!');
// dont' set the thing if we don't have a cache key, log errors on errors
if ($this->_key != false)
$result = $this->_cache->set($this->_key, $value, $flags, $expire);
else
throw new BSException("unable to cache an item because the key was not defined!");
if (!$result && $throwOnFail)
throw new BSException("setting the cache value for '{$this->_origKey}' failed! [$result]");
// some keys are paginated, we need to save (in memcache) the list of pages
// that has been created for this cached paginated item
if ($this->_savePage)
$this->savePage($expire);
return $result;
}
/**
* delete the cached value for the current key, see createKey to define the
* key to get the cache value for. If the key exists, an exception is thrown
*
* @see createKey for how to define the cache key
* @see createNameSpaceKey for how to define the cache key
* @param boolean $throwOnFail throw an exception if the delete fails (default false)
* @throws BSException if setting the key fails, or it already exists
* @throws BSException if memcache is not connected
* @return true on success
*/
public function delete($throwOnFail = false)
{
if (!LIB_CACHE_ENABLE)
return $this->_log->info('no cache delete, cache not enabled');
$this->_log->logTrace('Deleting Cache entry by key: '.$this->_key);
// don't even attempt if memcache is not connectecd
if (!$this->isConnected())
return $this->_log->warn('memcache is not connected, delete failed!');
// dont' set the thing if we don't have a cache key, log errors on errors
if ($this->_key != false)
$result = $this->_cache->delete($this->_key);
else
throw new CacheNotDefinedException($this->_key);
if (!$result && $throwOnFail == true)
throw new CacheDeleteException($this->_origKey);
return $result;
}
/**
* Passthrough for the flush method on the memcache object.
*/
public function flush()
{
return $this->_cache->flush();
}
/**
* Passthrough method to get stats from memcache object
* @return array stats from memcache object
*/
public function getstats()
{
return $this->_cache->getstats();
}
}