source: trunk/lib/Cache.inc.php

Last change on this file was 638, checked in by anonymous, 6 years ago

Add cache auto expires timeout

File size: 11.9 KB
RevLine 
[1]1<?php
2/**
[362]3 * The Strangecode Codebase - a general application development framework for PHP
4 * For details visit the project site: <http://trac.strangecode.com/codebase/>
[396]5 * Copyright 2001-2012 Strangecode, LLC
[468]6 *
[362]7 * This file is part of The Strangecode Codebase.
8 *
9 * The Strangecode Codebase is free software: you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as published by the
11 * Free Software Foundation, either version 3 of the License, or (at your option)
12 * any later version.
[468]13 *
[362]14 * The Strangecode Codebase is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 * details.
[468]18 *
[362]19 * You should have received a copy of the GNU General Public License along with
20 * The Strangecode Codebase. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23/**
[136]24 * Cache.inc.php
25 *
[1]26 * Provides an API for storing a limited amount of data
27 * intended to have a short lifetime in a user's session.
28 *
[534]29 * Disable cache per-request by adding '_disable_cache=1' to a GET or POST parameter.
30 *
[1]31 * @author  Quinn Comendant <quinn@strangecode.com>
[136]32 * @version 2.1
[1]33 * @since   2001
34 */
[136]35
[502]36class Cache
37{
[136]38
[468]39    // A place to keep object instances for the singleton pattern.
[484]40    protected static $instances = array();
[468]41
[152]42    // Namespace of this instance of Prefs.
[484]43    protected $_ns;
[152]44
45    // Configuration parameters for this object.
[484]46    protected $_params = array(
[468]47
48        // Type of cache. Currently only 'session' is supported.
49        'type' => 'session',
50
[334]51        // If false nothing will be cached or retrieved. Useful for testing realtime data requests.
[21]52        'enabled' => true,
[152]53
54        // The maximum size in bytes of any one variable.
55        'item_size_limit' => 4194304, // 4 MB
[468]56
[152]57        // The maximum size in bytes before the cache will begin flushing out old items.
58        'stack_size_limit' => 4194304, // 4 MB
[468]59
[152]60        // The minimum items to keep in the cache regardless of item or cache size.
61        'min_items' => 5,
[638]62
63        // How long in seconds before items in the cache are considered expired.
64        // When the cache expires, Cache->get() returns null.
65        'expires' => 3600,
[1]66    );
[468]67
[152]68    /*
[537]69    * Constructor. This is publicly accessible for compatibility with older implementations,
[468]70    * but the preferred method of instantiation is by use of the singleton pattern:
71    *   $cache =& Cache::getInstance('namespace');
72    *   $cache->setParam(array('enabled' => true));
[152]73    *
74    * @access   public
75    * @param    string  $namespace  This object will store data under this realm.
76    * @author   Quinn Comendant <quinn@strangecode.com>
77    * @version  1.0
78    * @since    05 Jun 2006 23:14:21
79    */
[468]80    public function __construct($namespace='')
[152]81    {
[172]82        $app =& App::getInstance();
[468]83
[154]84        $this->_ns = $namespace;
[172]85
86        if (true !== $app->getParam('enable_session')) {
[523]87            // Force disable the cache because there is no session to save to.
[534]88            $app->logMsg('Cache disabled, enable_session != true.', LOG_DEBUG, __FILE__, __LINE__);
[172]89            $this->setParam(array('enabled' => false));
[523]90        } else if (!isset($_SESSION['_cache'][$this->_ns])) {
91            // Otherwise, clear to initialize the session variable.
[152]92            $this->clear();
93        }
94    }
[1]95
96    /**
97     * This method enforces the singleton pattern for this class.
98     *
[136]99     * @return  object  Reference to the global Cache object.
[1]100     * @access  public
101     * @static
102     */
[468]103    public static function &getInstance($namespace='')
[136]104    {
[468]105        if (!array_key_exists($namespace, self::$instances)) {
106            self::$instances[$namespace] = new self($namespace);
[1]107        }
[468]108        return self::$instances[$namespace];
[1]109    }
110
111    /**
112     * Set (or overwrite existing) parameters by passing an array of new parameters.
113     *
114     * @access public
115     * @param  array    $params     Array of parameters (key => val pairs).
116     */
[468]117    public function setParam($params)
[1]118    {
[479]119        $app =& App::getInstance();
[21]120
[1]121        if (isset($params) && is_array($params)) {
122            // Merge new parameters with old overriding only those passed.
[136]123            $this->_params = array_merge($this->_params, $params);
[1]124        } else {
[136]125            $app->logMsg(sprintf('Parameters are not an array: %s', $params), LOG_ERR, __FILE__, __LINE__);
[1]126        }
127    }
128
129    /**
130     * Return the value of a parameter, if it exists.
131     *
132     * @access public
133     * @param string $param        Which parameter to return.
134     * @return mixed               Configured parameter value.
135     */
[468]136    public function getParam($param)
[1]137    {
[479]138        $app =& App::getInstance();
[468]139
[478]140        if (array_key_exists($param, $this->_params)) {
[136]141            return $this->_params[$param];
[1]142        } else {
[146]143            $app->logMsg(sprintf('Parameter is not set: %s', $param), LOG_DEBUG, __FILE__, __LINE__);
[1]144            return null;
145        }
146    }
147
148    /**
[334]149     * Stores a new variable in the session cache. The $key should not be numeric
[468]150     * because the array_shift function will reset the key to the next largest
[174]151     * int key. Weird behavior I can't understand. For example $cache["123"] will become $cache[0]
[1]152     *
[468]153     * @param str   $key                An identifier for the cached object.
154     * @param mixed $var                The data to store in the session cache.
155     * @param bool  $allow_oversized    If we have something really big that we still want to cache, setting this to true allows this.
156     * @return bool                     True on success, false otherwise.
[1]157     */
[468]158    public function set($key, $var, $allow_oversized=false)
[1]159    {
[479]160        $app =& App::getInstance();
[1]161
[534]162        if (true !== $this->getParam('enabled') || getFormData('_disable_cache')) {
[316]163            $app->logMsg(sprintf('Cache disabled, not saving data.', null), LOG_DEBUG, __FILE__, __LINE__);
[21]164            return false;
165        }
166
[468]167        if (is_numeric($key)) {
168            $app->logMsg(sprintf('Cache::set key value should not be numeric (%s given)', $key), LOG_WARNING, __FILE__, __LINE__);
169        }
170
[152]171        $var = serialize($var);
[247]172        $var_len = mb_strlen($var);
[42]173
[152]174        if ($var_len >= $this->getParam('item_size_limit')) {
175            $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__);
[1]176            return false;
177        }
[42]178
[468]179        if ($allow_oversized && $var_len >= $this->getParam('stack_size_limit')) {
[152]180            $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__);
[1]181            return false;
[468]182        }
[1]183
[152]184        // Remove any value already stored under this key.
[174]185        unset($_SESSION['_cache'][$this->_ns][$key]);
[152]186
187        // Continue to prune the cache if its size is greater than stack_size_limit, but keep at least min_items.
[247]188        while (mb_strlen(serialize($_SESSION['_cache'][$this->_ns])) + $var_len >= $this->getParam('stack_size_limit') && sizeof($_SESSION['_cache'][$this->_ns]) >= $this->getParam('min_items')) {
[154]189            array_shift($_SESSION['_cache'][$this->_ns]);
[1]190        }
[42]191
[152]192        // Save this value under the specified key.
[638]193        $_SESSION['_cache'][$this->_ns][$key] = array(
194            'var' => $var,
195            'time' => time(),
196        );
[152]197
[558]198        $app->logMsg(sprintf('Set cache item “%s”', $key), LOG_DEBUG, __FILE__, __LINE__);
199
[152]200        if ($var_len >= 1024000) {
201            $app->logMsg(sprintf('Successfully cached oversized variable (%s bytes).', $var_len), LOG_DEBUG, __FILE__, __LINE__);
[1]202        }
[42]203
[136]204        return true;
[1]205    }
[42]206
[1]207    /**
[334]208     * Retrieves an object from the session cache and returns it unserialized.
[1]209     * It also moves it to the top of the stack, which makes it such that the
210     * cache flushing mechanism of putCache deletes the oldest referenced items
211     * first.
212     *
[136]213     * @param string $key  The key for the datum to retrieve.
[1]214     * @return mixed          The requested datum, or false on failure.
215     */
[468]216    public function get($key)
[1]217    {
[153]218        $app =& App::getInstance();
[162]219
[534]220        if (true !== $this->getParam('enabled') || getFormData('_disable_cache')) {
[316]221            $app->logMsg(sprintf('Cache disabled, not getting data.', null), LOG_DEBUG, __FILE__, __LINE__);
[21]222            return false;
223        }
[42]224
[480]225        if (isset($_SESSION['_cache'][$this->_ns]) && array_key_exists($key, $_SESSION['_cache'][$this->_ns])) {
[638]226            if (isset($_SESSION['_cache'][$this->_ns][$key]['time']) && $_SESSION['_cache'][$this->_ns][$key]['time'] > (time() - $this->getParam('expires'))) {
227                $app->logMsg(sprintf('Retrieving %s from cache.', $key), LOG_DEBUG, __FILE__, __LINE__);
228                // Move the accessed cached datum to the top of the stack. Maybe somebody knows a better way to do this?
229                $tmp =& $_SESSION['_cache'][$this->_ns][$key];
230                unset($_SESSION['_cache'][$this->_ns][$key]);
231                $_SESSION['_cache'][$this->_ns][$key] =& $tmp;
232                // Return the unserialized datum.
233                return unserialize($_SESSION['_cache'][$this->_ns][$key]['var']);
234            } else {
235                // Cached item has expired.
236                $app->logMsg(sprintf('Cached %s expired %s ago', $key, humanTime(time() - $_SESSION['_cache'][$this->_ns][$key]['time'])), LOG_DEBUG, __FILE__, __LINE__);
237                return null;
238            }
[1]239        } else {
[172]240            $app->logMsg(sprintf('Missing %s from cache.', $key), LOG_DEBUG, __FILE__, __LINE__);
[1]241            return false;
242        }
243    }
[42]244
[1]245    /**
246     * Tells you if the object is cached.
247     *
[136]248     * @param string $key  The key of the object to check.
[218]249     * @return bool         True if a value exists for the given key.
[1]250     */
[468]251    public function exists($key)
[1]252    {
[405]253        $app =& App::getInstance();
254
[534]255        if (true !== $this->getParam('enabled') || getFormData('_disable_cache')) {
[405]256            $app->logMsg(sprintf('Cache disabled on exist assertion.', null), LOG_DEBUG, __FILE__, __LINE__);
[21]257            return false;
258        }
259
[638]260        if (isset($_SESSION['_cache'][$this->_ns])
261        && array_key_exists($key, $_SESSION['_cache'][$this->_ns])
262        && isset($_SESSION['_cache'][$this->_ns][$key]['time'])
263        && $_SESSION['_cache'][$this->_ns][$key]['time'] > (time() - $this->getParam('expires'))) {
264            $app->logMsg(sprintf('Cache item “%s” exists', $key), LOG_DEBUG, __FILE__, __LINE__);
265            return true;
266        } else {
267            $app->logMsg(sprintf('Cache item “%s” missing or expired', $key), LOG_DEBUG, __FILE__, __LINE__);
268            return false;
269        }
[1]270    }
[42]271
[1]272    /**
[188]273     * Removes a cached object.
[1]274     *
[136]275     * @param string $key  The key of the object to check.
[218]276     * @return bool         True if the value existed before being unset.
[1]277     */
[468]278    public function delete($key)
[1]279    {
[523]280        $app =& App::getInstance();
281
[534]282        if (true !== $this->getParam('enabled') || getFormData('_disable_cache')) {
[523]283            $app->logMsg(sprintf('Cache disabled, skipping delete of %s', $key), LOG_DEBUG, __FILE__, __LINE__);
284            return false;
285        }
286
[480]287        if (isset($_SESSION['_cache'][$this->_ns]) && array_key_exists($key, $_SESSION['_cache'][$this->_ns])) {
[558]288            $app->logMsg(sprintf('Deleting cache item “%s”', $key), LOG_DEBUG, __FILE__, __LINE__);
[218]289            unset($_SESSION['_cache'][$this->_ns][$key]);
290            return true;
291        } else {
292            return false;
293        }
[1]294    }
[468]295
[152]296    /*
297    * Delete all existing items from the cache.
298    *
299    * @access   public
300    * @author   Quinn Comendant <quinn@strangecode.com>
301    * @version  1.0
302    * @since    05 Jun 2006 23:51:34
303    */
[468]304    public function clear()
[152]305    {
[611]306        $app =& App::getInstance();
307
[154]308        $_SESSION['_cache'][$this->_ns] = array();
[611]309        $app->logMsg(sprintf('Cleared %s cache', $this->_ns), LOG_DEBUG, __FILE__, __LINE__);
[152]310    }
[1]311
[136]312// END Cache
[1]313}
314
Note: See TracBrowser for help on using the repository browser.