* 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' => 3600, ); /* * 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 }