cache.php 8.25 KB
Newer Older
Evan Prodromou's avatar
Evan Prodromou committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Cache interface plus default in-memory cache implementation
 *
 * PHP version 5
 *
 * LICENCE: This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  Cache
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

/**
 * Interface for caching
 *
Evan Prodromou's avatar
Evan Prodromou committed
33 34 35
 * An abstract interface for caching. Because we originally used the
 * Memcache plugin directly, the interface uses a small subset of the
 * Memcache interface.
Evan Prodromou's avatar
Evan Prodromou committed
36
 *
Evan Prodromou's avatar
Evan Prodromou committed
37 38 39 40 41 42
 * @category  Cache
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
Evan Prodromou's avatar
Evan Prodromou committed
43 44 45
 */
class Cache
{
46 47 48 49 50 51
    /**
     * @var array additional in-process cache for web requests;
     *      disabled on CLI, unsafe for long-running daemons
     */
    var $_items = array();
    var $_inlineCache = true;
Evan Prodromou's avatar
Evan Prodromou committed
52 53
    static $_inst = null;

54 55
    const COMPRESSED = 1;

56
    private function __construct() {
57 58 59 60 61 62
        // Potentially long-running daemons or maintenance scripts
        // should not use an in-process cache as it becomes out of
        // date.
        $this->_inlineCache = (php_sapi_name() != 'cli');
    }

Evan Prodromou's avatar
Evan Prodromou committed
63 64 65 66 67 68 69
    /**
     * Singleton constructor
     *
     * Use this to get the singleton instance of Cache.
     *
     * @return Cache cache object
     */
Evan Prodromou's avatar
Evan Prodromou committed
70 71 72 73 74 75 76 77 78
    static function instance()
    {
        if (is_null(self::$_inst)) {
            self::$_inst = new Cache();
        }

        return self::$_inst;
    }

Evan Prodromou's avatar
Evan Prodromou committed
79 80 81 82 83 84 85 86 87 88 89
    /**
     * Create a cache key from input text
     *
     * Builds a cache key from input text. Helps to namespace
     * the cache area (if shared with other applications or sites)
     * and prevent conflicts.
     *
     * @param string $extra the real part of the key
     *
     * @return string full key
     */
Evan Prodromou's avatar
Evan Prodromou committed
90 91
    static function key($extra)
    {
92
        $base_key = common_config('cache', 'base');
Evan Prodromou's avatar
Evan Prodromou committed
93 94

        if (empty($base_key)) {
95
            $base_key = self::keyize(common_config('site', 'name'));
Evan Prodromou's avatar
Evan Prodromou committed
96 97
        }

98
        return 'gnusocial:' . $base_key . ':' . $extra;
Evan Prodromou's avatar
Evan Prodromou committed
99 100
    }

101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
    /**
     * Create a cache key for data dependent on code
     *
     * For cache elements that are dependent on changes in code, this creates
     * a more-or-less fingerprint of the current running code and adds it to
     * the cache key. In the case of an upgrade of core, or addition or
     * removal of plugins, a new unique fingerprint is generated and used.
     * 
     * There can still be problems with a) differences in versions of the 
     * plugins and b) people running code between official versions. This is
     * usually a problem only for experienced users like developers, who know
     * how to clear their cache.
     *
     * For sites that run code between versions (like the status.net cloud),
     * there's an additional build number configuration setting.
     * 
     * @param string $extra the real part of the key
     *
     * @return string full key
     */
    
    static function codeKey($extra)
    {
        static $prefix = null;
	
        if (empty($prefix)) {
	    
mmn's avatar
mmn committed
128
            $names   = array();
129
	    
mmn's avatar
mmn committed
130
            foreach (GNUsocial::getActivePlugins() as $plugin=>$attrs) {
mmn's avatar
mmn committed
131
                $names[] = $plugin;
132 133 134 135 136 137 138 139 140 141
            }
	    
            asort($names);
	    
            // Unique enough.
	
            $uniq = crc32(implode(',', $names));

            $build = common_config('site', 'build');

142
            $prefix = GNUSOCIAL_VERSION.':'.$build.':'.$uniq;
143 144 145 146 147
        }
	
        return Cache::key($prefix.':'.$extra);
    }
    
Evan Prodromou's avatar
Evan Prodromou committed
148 149 150 151 152 153 154 155 156
    /**
     * Make a string suitable for use as a key
     *
     * Useful for turning primary keys of tables into cache keys.
     *
     * @param string $str string to turn into a key
     *
     * @return string keyized string
     */
Evan Prodromou's avatar
Evan Prodromou committed
157 158 159 160 161 162 163
    static function keyize($str)
    {
        $str = strtolower($str);
        $str = preg_replace('/\s/', '_', $str);
        return $str;
    }

Evan Prodromou's avatar
Evan Prodromou committed
164 165 166 167 168 169 170 171 172
    /**
     * Get a value associated with a key
     *
     * The value should have been set previously.
     *
     * @param string $key Lookup key
     *
     * @return string retrieved value or null if unfound
     */
Evan Prodromou's avatar
Evan Prodromou committed
173 174
    function get($key)
    {
175
        $value = false;
Evan Prodromou's avatar
Evan Prodromou committed
176

177
        common_perf_counter('Cache::get', $key);
Evan Prodromou's avatar
Evan Prodromou committed
178
        if (Event::handle('StartCacheGet', array(&$key, &$value))) {
179
            if ($this->_inlineCache && array_key_exists($key, $this->_items)) {
Brion Vibber's avatar
Brion Vibber committed
180
                $value = unserialize($this->_items[$key]);
Evan Prodromou's avatar
Evan Prodromou committed
181 182 183 184 185 186 187
            }
            Event::handle('EndCacheGet', array($key, &$value));
        }

        return $value;
    }

Evan Prodromou's avatar
Evan Prodromou committed
188 189 190 191 192
    /**
     * Set the value associated with a key
     *
     * @param string  $key    The key to use for lookups
     * @param string  $value  The value to store
193
     * @param integer $flag   Flags to use, may include Cache::COMPRESSED
Evan Prodromou's avatar
Evan Prodromou committed
194 195 196 197
     * @param integer $expiry Expiry value, mostly ignored
     *
     * @return boolean success flag
     */
Evan Prodromou's avatar
Evan Prodromou committed
198 199 200 201
    function set($key, $value, $flag=null, $expiry=null)
    {
        $success = false;

202
        common_perf_counter('Cache::set', $key);
Evan Prodromou's avatar
Evan Prodromou committed
203 204 205
        if (Event::handle('StartCacheSet', array(&$key, &$value, &$flag,
                                                 &$expiry, &$success))) {

206 207 208
            if ($this->_inlineCache) {
                $this->_items[$key] = serialize($value);
            }
Evan Prodromou's avatar
Evan Prodromou committed
209

Evan Prodromou's avatar
Evan Prodromou committed
210
            $success = true;
Evan Prodromou's avatar
Evan Prodromou committed
211 212 213

            Event::handle('EndCacheSet', array($key, $value, $flag,
                                               $expiry));
Evan Prodromou's avatar
Evan Prodromou committed
214 215 216 217 218
        }

        return $success;
    }

219 220 221 222 223 224 225 226 227 228 229 230
    /**
     * Atomically increment an existing numeric value.
     * Existing expiration time should remain unchanged, if any.
     *
     * @param string  $key    The key to use for lookups
     * @param int     $step   Amount to increment (default 1)
     *
     * @return mixed incremented value, or false if not set.
     */
    function increment($key, $step=1)
    {
        $value = false;
231
        common_perf_counter('Cache::increment', $key);
232 233 234 235 236 237 238 239 240 241 242 243 244 245
        if (Event::handle('StartCacheIncrement', array(&$key, &$step, &$value))) {
            // Fallback is not guaranteed to be atomic,
            // and may original expiry value.
            $value = $this->get($key);
            if ($value !== false) {
                $value += $step;
                $ok = $this->set($key, $value);
                $got = $this->get($key);
            }
            Event::handle('EndCacheIncrement', array($key, $step, $value));
        }
        return $value;
    }

Evan Prodromou's avatar
Evan Prodromou committed
246 247 248 249 250 251 252
    /**
     * Delete the value associated with a key
     *
     * @param string $key Key to delete
     *
     * @return boolean success flag
     */
Evan Prodromou's avatar
Evan Prodromou committed
253 254 255 256
    function delete($key)
    {
        $success = false;

257
        common_perf_counter('Cache::delete', $key);
Evan Prodromou's avatar
Evan Prodromou committed
258
        if (Event::handle('StartCacheDelete', array(&$key, &$success))) {
259
            if ($this->_inlineCache && array_key_exists($key, $this->_items)) {
260 261
                unset($this->_items[$key]);
            }
Evan Prodromou's avatar
Evan Prodromou committed
262 263 264 265 266 267
            $success = true;
            Event::handle('EndCacheDelete', array($key));
        }

        return $success;
    }
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285

    /**
     * Close or reconnect any remote connections, such as to give
     * daemon processes a chance to reconnect on a fresh socket.
     *
     * @return boolean success flag
     */
    function reconnect()
    {
        $success = false;

        if (Event::handle('StartCacheReconnect', array(&$success))) {
            $success = true;
            Event::handle('EndCacheReconnect', array());
        }

        return $success;
    }
Evan Prodromou's avatar
Evan Prodromou committed
286
}