Version: current

      1 <?php
      2 /**
      3  * SS_Cache provides a bunch of static functions wrapping the Zend_Cache system in something a little more
      4  * easy to use with the SilverStripe config system.
      5  *
      6  * A Zend_Cache has both a frontend (determines how to get the value to cache, and how to serialize it for storage)
      7  * and a backend (handles the actual storage).
      8  *
      9  * Rather than require library code to specify the backend directly, cache consumers provide a name for the cache
     10  * backend they want. The end developer can then specify which backend to use for each name in their project's
     11  * _config.php. They can also use 'all' to provide a backend for all named caches
     12  *
     13  * End developers provide a set of named backends, then pick the specific backend for each named cache. There is a
     14  * default File cache set up as the 'default' named backend, which is assigned to 'all' named caches
     15  *
     16  * USING A CACHE
     17  *
     18  * $cache = SS_Cache::factory('foo') ; // foo is any name (try to be specific), and is used to get configuration & storage info
     19  *
     20  * if (!($result = $cache->load($cachekey))) {
     21  *  $result = caluate some how;
     22  *  $cache->save($result);
     23  * }
     24  *
     25  * return $result;
     26  *
     27  * Normally there's no need to remove things from the cache - the cache backends clear out entries based on age & maximum
     28  * allocated storage. If you include the version of the object in the cache key, even object changes don't need any invalidation
     29  *
     30  * DISABLING CACHING IN DEV MOVE
     31  *
     32  * (in _config.php)
     33  *
     34  * if (Director::isDev()) SS_Cache::set_cache_lifetime('any', -1, 100);
     35  *
     36  * USING MEMCACHED AS STORE
     37  *
     38  * (in _config.php)
     39  *
     40  * SS_Cache::add_backend('primary_memcached', 'Memcached',
     41  *  array('host' => 'localhost', 'port' => 11211, 'persistent' => true, 'weight' => 1, 'timeout' => 5, 'retry_interval' => 15, 'status' => true, 'failure_callback' => '' )
     42  * );
     43  *
     44  * SS_Cache::pick_backend('primary_memcached', 'any', 10);
     45  * SS_Cache::pick_backend('default', 'aggregate', 20); // Aggregate needs a backend with tag support, which memcached doesn't provide
     46  *
     47  * USING APC AND FILE AS TWO LEVEL STORE
     48  *
     49  * (in _config.php)
     50  *
     51  * SS_Cache::add_backend('two-level', 'TwoLevels' array(
     52  *  'slow_backend' => 'File',
     53  *  'fast_backend' => 'Apc',
     54  *  'slow_backend_options' => array('cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache')
     55  * ));
     56  *
     57  * SS_Cache::pick_backend('two-level', 'any', 10); // No need for special backend for aggregate - TwoLevels with a File slow backend supports tags
     58  *
     59  * @author hfried
     60  * @package sapphire
     61  * @subpackage core
     62  */
     63 class SS_Cache {
     64
     65     protected static $backends = array();
     66     protected static $backend_picks = array();
     67
     68     protected static $cache_lifetime = array();
     69
     70     /**
     71      * Initialize the 'default' named cache backend
     72      */
     73     protected static function init(){
     74         if (!isset(self::$backends['default'])) {
     75             $cachedir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache';
     76             if (!is_dir($cachedir)) mkdir($cachedir);
     77             self::$backends['default'] = array('File', array('cache_dir' => TEMP_FOLDER . DIRECTORY_SEPARATOR . 'cache'));
     78         }
     79     }
     80
     81     /**
     82      * Add a new named cache backend
     83      *
     84      * @param string $name The name of this backend as a freeform string
     85      * @param string $type The Zend_Cache backend ('File' or 'Sqlite' or ...)
     86      * @param array $options The Zend_Cache backend options (see http://framework.zend.com/manual/en/zend.cache.html)
     87      * @return none
     88      */
     89     static function add_backend($name, $type, $options=array()) {
     90         self::init();
     91         self::$backends[$name] = array($type, $options);
     92     }
     93
     94     /**
     95      * Pick a named cache backend for a particular named cache
     96      *
     97      * @param string $name The name of the backend, as passed as the first argument to add_backend
     98      * @param string $for The name of the cache to pick this backend for (or 'any' for any backend)
     99      * @param integer $priority The priority of this pick - the call with the highest number will be the actual backend picked.
    100      *                          A backend picked for a specific cache name will always be used instead of 'any' if it exists, no matter the priority.
    101      * @return none
    102      */
    103     static function pick_backend($name, $for, $priority=1) {
    104         self::init();
    105
    106         $current = -1;
    107         if (isset(self::$backend_picks[$for])) $current = self::$backend_picks[$for]['priority'];
    108
    109         if ($priority >= $current) self::$backend_picks[$for] = array('name' => $name, 'priority' => $priority);
    110     }
    111
    112     /**
    113      * Set the cache lifetime for a particular named cache
    114      *
    115      * @param string $for The name of the cache to set this lifetime for (or 'any' for all backends)
    116      * @param integer $lifetime The lifetime of an item of the cache, in seconds, or -1 to disable caching
    117      * @param integer $priority The priority. The highest priority setting is used. Unlike backends, 'any' is not special in terms of priority.
    118      */
    119     static function set_cache_lifetime($for, $lifetime=600, $priority=1) {
    120         self::init();
    121
    122         $current = -1;
    123         if (isset(self::$cache_lifetime[$for])) $current = self::$cache_lifetime[$for]['priority'];
    124
    125         if ($priority >= $current) self::$cache_lifetime[$for] = array('lifetime' => $lifetime, 'priority' => $priority);
    126     }
    127
    128     /**
    129      * Build a cache object
    130      * @param string $for The name of the cache to build the cache object for
    131      * @param string $frontend (optional) The type of Zend_Cache frontend to use. Output is almost always the best
    132      * @param array $frontendOptions (optional) Any frontend options to use.
    133      *
    134      * @return The cache object. It has been partitioned so that actions on the object
    135      *  won't affect cache objects generated with a different $for value, even those using the same Zend_Backend
    136      *
    137      * -- Cache a calculation
    138      *
    139      * if (!($result = $cache->load($cachekey))) {
    140      *  $result = caluate some how;
    141      *  $cache->save($result);
    142      * }
    143      *
    144      * return $result;
    145      *
    146      * -- Cache captured output
    147      *
    148      * if (!($cache->start($cachekey))) {
    149      *
    150      *  // output everything as usual
    151      *  echo 'Hello world! ';
    152      *  echo 'This is cached ('.time().') ';
    153      *
    154      *  $cache->end(); // output buffering ends
    155      * }
    156      *
    157      * -- Invalidate an element
    158      *
    159      * $cache->remove($cachekey);
    160      *
    161      * -- Clear the cache (warning - this clears the entire backend, not just this named cache partition)
    162      *
    163      * $cache->clean(Zend_Cache::CLEANING_MODE_ALL);
    164      *
    165      * See the Zend_Cache documentation at http://framework.zend.com/manual/en/zend.cache.html for more
    166      *
    167      */
    168     static function factory($for, $frontend='Output', $frontendOptions=null) {
    169         self::init();
    170
    171         $backend_name = 'default'; $backend_priority = -1;
    172         $cache_lifetime = 600; $lifetime_priority = -1;
    173
    174         foreach (array('any', $for) as $name) {
    175             if (isset(self::$backend_picks[$name]) && self::$backend_picks[$name]['priority'] > $backend_priority) {
    176                 $backend_name = self::$backend_picks[$name]['name'];
    177                 $backend_priority = self::$backend_picks[$name]['priority'];
    178             }
    179
    180             if (isset(self::$cache_lifetime[$name]) && self::$cache_lifetime[$name]['priority'] > $lifetime_priority) {
    181                 $cache_lifetime = self::$cache_lifetime[$name]['lifetime'];
    182                 $lifetime_priority = self::$cache_lifetime[$name]['priority'];
    183             }
    184         }
    185
    186         $backend = self::$backends[$backend_name];
    187
    188         $basicOptions = array('cache_id_prefix' => $for);
    189
    190         if ($cache_lifetime >= 0) $basicOptions['lifetime'] = $cache_lifetime;
    191         else $basicOptions['caching'] = false;
    192
    193         $frontendOptions = $frontendOptions ? array_merge($basicOptions, $frontendOptions) : $basicOptions;
    194
    195         require_once 'Zend/Cache.php';
    196         return Zend_Cache::factory($frontend, $backend[0], $frontendOptions, $backend[1]);
    197     }
    198 }

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -