* Copyright 2001-2012 Strangecode, LLC
*
* This file is part of The Strangecode Codebase.
*
* The Strangecode Codebase is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* The Strangecode Codebase is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* The Strangecode Codebase. If not, see .
*/
/**
* Cache.inc.php
*
* Provides an API for storing a limited amount of data
* intended to have a short lifetime in a user's session.
*
* Disable cache per-request by adding '_disable_cache=1' to a GET or POST parameter.
*
* @author Quinn Comendant
* @version 2.1
* @since 2001
*/
class Cache
{
// A place to keep object instances for the singleton pattern.
protected static $instances = array();
// Namespace of this instance of Prefs.
protected $_ns;
// Configuration parameters for this object.
protected $_params = array(
// Type of cache. Currently only 'session' is supported.
'type' => 'session',
// If false nothing will be cached or retrieved. Useful for testing realtime data requests.
'enabled' => true,
// The maximum size in bytes of any one variable.
'item_size_limit' => 4194304, // 4 MB
// The maximum size in bytes before the cache will begin flushing out old items.
'stack_size_limit' => 4194304, // 4 MB
// The minimum items to keep in the cache regardless of item or cache size.
'min_items' => 5,
// How long in seconds before items in the cache are considered expired.
// When the cache expires, Cache->get() returns null.
'expires' => 900,
);
/*
* Constructor. This is publicly accessible for compatibility with older implementations,
* but the preferred method of instantiation is by use of the singleton pattern:
* $cache =& Cache::getInstance('namespace');
* $cache->setParam(array('enabled' => true));
*
* @access public
* @param string $namespace This object will store data under this realm.
* @author Quinn Comendant
* @version 1.0
* @since 05 Jun 2006 23:14:21
*/
public function __construct($namespace='')
{
$app =& App::getInstance();
$this->_ns = $namespace;
if (true !== $app->getParam('enable_session')) {
// Force disable the cache because there is no session to save to.
$app->logMsg('Cache disabled, enable_session != true.', LOG_DEBUG, __FILE__, __LINE__);
$this->setParam(array('enabled' => false));
} else if (!isset($_SESSION['_cache'][$this->_ns])) {
// Otherwise, clear to initialize the session variable.
$this->clear();
}
}
/**
* This method enforces the singleton pattern for this class.
*
* @return object Reference to the global Cache object.
* @access public
* @static
*/
public static function &getInstance($namespace='')
{
if (!array_key_exists($namespace, self::$instances)) {
self::$instances[$namespace] = new self($namespace);
}
return self::$instances[$namespace];
}
/**
* Set (or overwrite existing) parameters by passing an array of new parameters.
*
* @access public
* @param array $params Array of parameters (key => val pairs).
*/
public function setParam($params)
{
$app =& App::getInstance();
if (isset($params) && is_array($params)) {
// Merge new parameters with old overriding only those passed.
$this->_params = array_merge($this->_params, $params);
} else {
$app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
}
}
/**
* Return the value of a parameter, if it exists.
*
* @access public
* @param string $param Which parameter to return.
* @return mixed Configured parameter value.
*/
public function getParam($param)
{
$app =& App::getInstance();
if (array_key_exists($param, $this->_params)) {
return $this->_params[$param];
} else {
$app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
return null;
}
}
/**
* Stores a new variable in the session cache. The $key should not be numeric
* because the array_shift function will reset the key to the next largest
* int key. Weird behavior I can't understand. For example $cache["123"] will become $cache[0]
*
* @param str $key An identifier for the cached object.
* @param mixed $var The data to store in the session cache.
* @param bool $allow_oversized If we have something really big that we still want to cache, setting this to true allows this.
* @return bool True on success, false otherwise.
*/
public function set($key, $var, $allow_oversized=false)
{
$app =& App::getInstance();
if (true !== $this->getParam('enabled') || getFormData('_disable_cache')) {
$app->logMsg(sprintf('Cache disabled, not saving data.', null), LOG_DEBUG, __FILE__, __LINE__);
return false;
}
if (is_numeric($key)) {
$app->logMsg(sprintf('Cache::set key value should not be numeric (%s given)', $key), LOG_WARNING, __FILE__, __LINE__);
}
$var = serialize($var);
$var_len = mb_strlen($var);
if ($var_len >= $this->getParam('item_size_limit')) {
$app->logMsg(sprintf('Serialized variable (%s bytes) more than item_size_limit (%s bytes).', $var_len, $this->getParam('item_size_limit')), LOG_NOTICE, __FILE__, __LINE__);
return false;
}
if ($allow_oversized && $var_len >= $this->getParam('stack_size_limit')) {
$app->logMsg(sprintf('Serialized variable (%s bytes) more than stack_size_limit (%s bytes).', $var_len, $this->getParam('stack_size_limit')), LOG_NOTICE, __FILE__, __LINE__);
return false;
}
// Remove any value already stored under this key.
unset($_SESSION['_cache'][$this->_ns][$key]);
// Continue to prune the cache if its size is greater than stack_size_limit, but keep at least min_items.
while (mb_strlen(serialize($_SESSION['_cache'][$this->_ns])) + $var_len >= $this->getParam('stack_size_limit') && sizeof($_SESSION['_cache'][$this->_ns]) >= $this->getParam('min_items')) {
array_shift($_SESSION['_cache'][$this->_ns]);
}
// Save this value under the specified key.
$_SESSION['_cache'][$this->_ns][$key] = array(
'var' => $var,
'time' => time(),
);
$app->logMsg(sprintf('Set cache item “%s”', $key), LOG_DEBUG, __FILE__, __LINE__);
if ($var_len >= 1024000) {
$app->logMsg(sprintf('Successfully cached oversized variable (%s bytes).', $var_len), LOG_DEBUG, __FILE__, __LINE__);
}
return true;
}
/**
* Retrieves an object from the session cache and returns it unserialized.
* It also moves it to the top of the stack, which makes it such that the
* cache flushing mechanism of putCache deletes the oldest referenced items
* first.
*
* @param string $key The key for the datum to retrieve.
* @return mixed The requested datum, or false on failure.
*/
public function get($key)
{
$app =& App::getInstance();
if (true !== $this->getParam('enabled') || getFormData('_disable_cache')) {
$app->logMsg(sprintf('Cache disabled, not getting data.', null), LOG_DEBUG, __FILE__, __LINE__);
return false;
}
if (isset($_SESSION['_cache'][$this->_ns]) && array_key_exists($key, $_SESSION['_cache'][$this->_ns])) {
if (isset($_SESSION['_cache'][$this->_ns][$key]['time']) && $_SESSION['_cache'][$this->_ns][$key]['time'] > (time() - $this->getParam('expires'))) {
$app->logMsg(sprintf('Retrieving %s from cache.', $key), LOG_DEBUG, __FILE__, __LINE__);
// Move the accessed cached datum to the top of the stack. Maybe somebody knows a better way to do this?
$tmp =& $_SESSION['_cache'][$this->_ns][$key];
unset($_SESSION['_cache'][$this->_ns][$key]);
$_SESSION['_cache'][$this->_ns][$key] =& $tmp;
// Return the unserialized datum.
return unserialize($_SESSION['_cache'][$this->_ns][$key]['var']);
} else {
// Cached item has expired.
$app->logMsg(sprintf('Cached %s expired %s ago', $key, humanTime(time() - $_SESSION['_cache'][$this->_ns][$key]['time'])), LOG_DEBUG, __FILE__, __LINE__);
return null;
}
} else {
$app->logMsg(sprintf('Missing %s from cache.', $key), LOG_DEBUG, __FILE__, __LINE__);
return false;
}
}
/**
* Tells you if the object is cached.
*
* @param string $key The key of the object to check.
* @return bool True if a value exists for the given key.
*/
public function exists($key)
{
$app =& App::getInstance();
if (true !== $this->getParam('enabled') || getFormData('_disable_cache')) {
$app->logMsg(sprintf('Cache disabled on exist assertion.', null), LOG_DEBUG, __FILE__, __LINE__);
return false;
}
if (isset($_SESSION['_cache'][$this->_ns])
&& array_key_exists($key, $_SESSION['_cache'][$this->_ns])
&& isset($_SESSION['_cache'][$this->_ns][$key]['time'])
&& $_SESSION['_cache'][$this->_ns][$key]['time'] > (time() - $this->getParam('expires'))) {
$app->logMsg(sprintf('Cache item “%s” exists', $key), LOG_DEBUG, __FILE__, __LINE__);
return true;
} else {
$app->logMsg(sprintf('Cache item “%s” missing or expired', $key), LOG_DEBUG, __FILE__, __LINE__);
return false;
}
}
/**
* Removes a cached object.
*
* @param string $key The key of the object to check.
* @return bool True if the value existed before being unset.
*/
public function delete($key)
{
$app =& App::getInstance();
if (true !== $this->getParam('enabled') || getFormData('_disable_cache')) {
$app->logMsg(sprintf('Cache disabled, skipping delete of %s', $key), LOG_DEBUG, __FILE__, __LINE__);
return false;
}
if (isset($_SESSION['_cache'][$this->_ns]) && array_key_exists($key, $_SESSION['_cache'][$this->_ns])) {
$app->logMsg(sprintf('Deleting cache item “%s”', $key), LOG_DEBUG, __FILE__, __LINE__);
unset($_SESSION['_cache'][$this->_ns][$key]);
return true;
} else {
return false;
}
}
/*
* Delete all existing items from the cache.
*
* @access public
* @author Quinn Comendant
* @version 1.0
* @since 05 Jun 2006 23:51:34
*/
public function clear()
{
$app =& App::getInstance();
$_SESSION['_cache'][$this->_ns] = array();
$app->logMsg(sprintf('Cleared %s cache', $this->_ns), LOG_DEBUG, __FILE__, __LINE__);
}
// END Cache
}