web/lib/Zend/Cache/Backend/TwoLevels.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 /**
       
     3  * Zend Framework
       
     4  *
       
     5  * LICENSE
       
     6  *
       
     7  * This source file is subject to the new BSD license that is bundled
       
     8  * with this package in the file LICENSE.txt.
       
     9  * It is also available through the world-wide-web at this URL:
       
    10  * http://framework.zend.com/license/new-bsd
       
    11  * If you did not receive a copy of the license and are unable to
       
    12  * obtain it through the world-wide-web, please send an email
       
    13  * to license@zend.com so we can send you a copy immediately.
       
    14  *
       
    15  * @category   Zend
       
    16  * @package    Zend_Cache
       
    17  * @subpackage Zend_Cache_Backend
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  * @version    $Id: TwoLevels.php 22736 2010-07-30 16:25:54Z andyfowler $
       
    21  */
       
    22 
       
    23 
       
    24 /**
       
    25  * @see Zend_Cache_Backend_ExtendedInterface
       
    26  */
       
    27 require_once 'Zend/Cache/Backend/ExtendedInterface.php';
       
    28 
       
    29 /**
       
    30  * @see Zend_Cache_Backend
       
    31  */
       
    32 require_once 'Zend/Cache/Backend.php';
       
    33 
       
    34 
       
    35 /**
       
    36  * @package    Zend_Cache
       
    37  * @subpackage Zend_Cache_Backend
       
    38  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    39  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    40  */
       
    41 
       
    42 class Zend_Cache_Backend_TwoLevels extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
       
    43 {
       
    44     /**
       
    45      * Available options
       
    46      *
       
    47      * =====> (string) slow_backend :
       
    48      * - Slow backend name
       
    49      * - Must implement the Zend_Cache_Backend_ExtendedInterface
       
    50      * - Should provide a big storage
       
    51      *
       
    52      * =====> (string) fast_backend :
       
    53      * - Flow backend name
       
    54      * - Must implement the Zend_Cache_Backend_ExtendedInterface
       
    55      * - Must be much faster than slow_backend
       
    56      *
       
    57      * =====> (array) slow_backend_options :
       
    58      * - Slow backend options (see corresponding backend)
       
    59      *
       
    60      * =====> (array) fast_backend_options :
       
    61      * - Fast backend options (see corresponding backend)
       
    62      *
       
    63      * =====> (int) stats_update_factor :
       
    64      * - Disable / Tune the computation of the fast backend filling percentage
       
    65      * - When saving a record into cache :
       
    66      *     1               => systematic computation of the fast backend filling percentage
       
    67      *     x (integer) > 1 => computation of the fast backend filling percentage randomly 1 times on x cache write
       
    68      *
       
    69      * =====> (boolean) slow_backend_custom_naming :
       
    70      * =====> (boolean) fast_backend_custom_naming :
       
    71      * =====> (boolean) slow_backend_autoload :
       
    72      * =====> (boolean) fast_backend_autoload :
       
    73      * - See Zend_Cache::factory() method
       
    74      *
       
    75      * =====> (boolean) auto_refresh_fast_cache
       
    76      * - If true, auto refresh the fast cache when a cache record is hit
       
    77      *
       
    78      * @var array available options
       
    79      */
       
    80     protected $_options = array(
       
    81         'slow_backend' => 'File',
       
    82         'fast_backend' => 'Apc',
       
    83         'slow_backend_options' => array(),
       
    84         'fast_backend_options' => array(),
       
    85         'stats_update_factor' => 10,
       
    86         'slow_backend_custom_naming' => false,
       
    87         'fast_backend_custom_naming' => false,
       
    88         'slow_backend_autoload' => false,
       
    89         'fast_backend_autoload' => false,
       
    90         'auto_refresh_fast_cache' => true
       
    91     );
       
    92 
       
    93     /**
       
    94      * Slow Backend
       
    95      *
       
    96      * @var Zend_Cache_Backend_ExtendedInterface
       
    97      */
       
    98     protected $_slowBackend;
       
    99 
       
   100     /**
       
   101      * Fast Backend
       
   102      *
       
   103      * @var Zend_Cache_Backend_ExtendedInterface
       
   104      */
       
   105     protected $_fastBackend;
       
   106 
       
   107     /**
       
   108      * Cache for the fast backend filling percentage
       
   109      *
       
   110      * @var int
       
   111      */
       
   112     protected $_fastBackendFillingPercentage = null;
       
   113 
       
   114     /**
       
   115      * Constructor
       
   116      *
       
   117      * @param  array $options Associative array of options
       
   118      * @throws Zend_Cache_Exception
       
   119      * @return void
       
   120      */
       
   121     public function __construct(array $options = array())
       
   122     {
       
   123         parent::__construct($options);
       
   124 
       
   125         if ($this->_options['slow_backend'] === null) {
       
   126             Zend_Cache::throwException('slow_backend option has to set');
       
   127         } elseif ($this->_options['slow_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) {
       
   128             $this->_slowBackend = $this->_options['slow_backend'];
       
   129         } else {
       
   130             $this->_slowBackend = Zend_Cache::_makeBackend(
       
   131                 $this->_options['slow_backend'],
       
   132                 $this->_options['slow_backend_options'],
       
   133                 $this->_options['slow_backend_custom_naming'],
       
   134                 $this->_options['slow_backend_autoload']
       
   135             );
       
   136             if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_slowBackend))) {
       
   137                 Zend_Cache::throwException('slow_backend must implement the Zend_Cache_Backend_ExtendedInterface interface');
       
   138             }
       
   139         }
       
   140 
       
   141         if ($this->_options['fast_backend'] === null) {
       
   142             Zend_Cache::throwException('fast_backend option has to set');
       
   143         } elseif ($this->_options['fast_backend'] instanceof Zend_Cache_Backend_ExtendedInterface) {
       
   144             $this->_fastBackend = $this->_options['fast_backend'];
       
   145         } else {
       
   146             $this->_fastBackend = Zend_Cache::_makeBackend(
       
   147                 $this->_options['fast_backend'],
       
   148                 $this->_options['fast_backend_options'],
       
   149                 $this->_options['fast_backend_custom_naming'],
       
   150                 $this->_options['fast_backend_autoload']
       
   151             );
       
   152             if (!in_array('Zend_Cache_Backend_ExtendedInterface', class_implements($this->_fastBackend))) {
       
   153                 Zend_Cache::throwException('fast_backend must implement the Zend_Cache_Backend_ExtendedInterface interface');
       
   154             }
       
   155         }
       
   156 
       
   157         $this->_slowBackend->setDirectives($this->_directives);
       
   158         $this->_fastBackend->setDirectives($this->_directives);
       
   159     }
       
   160 
       
   161     /**
       
   162      * Test if a cache is available or not (for the given id)
       
   163      *
       
   164      * @param  string $id cache id
       
   165      * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
       
   166      */
       
   167     public function test($id)
       
   168     {
       
   169         $fastTest = $this->_fastBackend->test($id);
       
   170         if ($fastTest) {
       
   171             return $fastTest;
       
   172         } else {
       
   173             return $this->_slowBackend->test($id);
       
   174         }
       
   175     }
       
   176 
       
   177     /**
       
   178      * Save some string datas into a cache record
       
   179      *
       
   180      * Note : $data is always "string" (serialization is done by the
       
   181      * core not by the backend)
       
   182      *
       
   183      * @param  string $data            Datas to cache
       
   184      * @param  string $id              Cache id
       
   185      * @param  array $tags             Array of strings, the cache record will be tagged by each string entry
       
   186      * @param  int   $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
       
   187      * @param  int   $priority         integer between 0 (very low priority) and 10 (maximum priority) used by some particular backends
       
   188      * @return boolean true if no problem
       
   189      */
       
   190     public function save($data, $id, $tags = array(), $specificLifetime = false, $priority = 8)
       
   191     {
       
   192         $usage = $this->_getFastFillingPercentage('saving');
       
   193         $boolFast = true;
       
   194         $lifetime = $this->getLifetime($specificLifetime);
       
   195         $preparedData = $this->_prepareData($data, $lifetime, $priority);
       
   196         if (($priority > 0) && (10 * $priority >= $usage)) {
       
   197             $fastLifetime = $this->_getFastLifetime($lifetime, $priority);
       
   198             $boolFast = $this->_fastBackend->save($preparedData, $id, array(), $fastLifetime);
       
   199             $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime);
       
   200         } else {
       
   201             $boolSlow = $this->_slowBackend->save($preparedData, $id, $tags, $lifetime);
       
   202             if ($boolSlow === true) {
       
   203                 $boolFast = $this->_fastBackend->remove($id);
       
   204                 if (!$boolFast && !$this->_fastBackend->test($id)) {
       
   205                     // some backends return false on remove() even if the key never existed. (and it won't if fast is full)
       
   206                     // all we care about is that the key doesn't exist now
       
   207                     $boolFast = true;
       
   208                 }
       
   209             }
       
   210         }
       
   211 
       
   212         return ($boolFast && $boolSlow);
       
   213     }
       
   214 
       
   215     /**
       
   216      * Test if a cache is available for the given id and (if yes) return it (false else)
       
   217      *
       
   218      * Note : return value is always "string" (unserialization is done by the core not by the backend)
       
   219      *
       
   220      * @param  string  $id                     Cache id
       
   221      * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
       
   222      * @return string|false cached datas
       
   223      */
       
   224     public function load($id, $doNotTestCacheValidity = false)
       
   225     {
       
   226         $res = $this->_fastBackend->load($id, $doNotTestCacheValidity);
       
   227         if ($res === false) {
       
   228             $res = $this->_slowBackend->load($id, $doNotTestCacheValidity);
       
   229             if ($res === false) {
       
   230                 // there is no cache at all for this id
       
   231                 return false;
       
   232             }
       
   233         }
       
   234         $array = unserialize($res);
       
   235         // maybe, we have to refresh the fast cache ?
       
   236         if ($this->_options['auto_refresh_fast_cache']) {
       
   237             if ($array['priority'] == 10) {
       
   238                 // no need to refresh the fast cache with priority = 10
       
   239                 return $array['data'];
       
   240             }
       
   241             $newFastLifetime = $this->_getFastLifetime($array['lifetime'], $array['priority'], time() - $array['expire']);
       
   242             // we have the time to refresh the fast cache
       
   243             $usage = $this->_getFastFillingPercentage('loading');
       
   244             if (($array['priority'] > 0) && (10 * $array['priority'] >= $usage)) {
       
   245                 // we can refresh the fast cache
       
   246                 $preparedData = $this->_prepareData($array['data'], $array['lifetime'], $array['priority']);
       
   247                 $this->_fastBackend->save($preparedData, $id, array(), $newFastLifetime);
       
   248             }
       
   249         }
       
   250         return $array['data'];
       
   251     }
       
   252 
       
   253     /**
       
   254      * Remove a cache record
       
   255      *
       
   256      * @param  string $id Cache id
       
   257      * @return boolean True if no problem
       
   258      */
       
   259     public function remove($id)
       
   260     {
       
   261         $boolFast = $this->_fastBackend->remove($id);
       
   262         $boolSlow = $this->_slowBackend->remove($id);
       
   263         return $boolFast && $boolSlow;
       
   264     }
       
   265 
       
   266     /**
       
   267      * Clean some cache records
       
   268      *
       
   269      * Available modes are :
       
   270      * Zend_Cache::CLEANING_MODE_ALL (default)    => remove all cache entries ($tags is not used)
       
   271      * Zend_Cache::CLEANING_MODE_OLD              => remove too old cache entries ($tags is not used)
       
   272      * Zend_Cache::CLEANING_MODE_MATCHING_TAG     => remove cache entries matching all given tags
       
   273      *                                               ($tags can be an array of strings or a single string)
       
   274      * Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG => remove cache entries not {matching one of the given tags}
       
   275      *                                               ($tags can be an array of strings or a single string)
       
   276      * Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG => remove cache entries matching any given tags
       
   277      *                                               ($tags can be an array of strings or a single string)
       
   278      *
       
   279      * @param  string $mode Clean mode
       
   280      * @param  array  $tags Array of tags
       
   281      * @throws Zend_Cache_Exception
       
   282      * @return boolean true if no problem
       
   283      */
       
   284     public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
       
   285     {
       
   286         switch($mode) {
       
   287             case Zend_Cache::CLEANING_MODE_ALL:
       
   288                 $boolFast = $this->_fastBackend->clean(Zend_Cache::CLEANING_MODE_ALL);
       
   289                 $boolSlow = $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_ALL);
       
   290                 return $boolFast && $boolSlow;
       
   291                 break;
       
   292             case Zend_Cache::CLEANING_MODE_OLD:
       
   293                 return $this->_slowBackend->clean(Zend_Cache::CLEANING_MODE_OLD);
       
   294             case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
       
   295                 $ids = $this->_slowBackend->getIdsMatchingTags($tags);
       
   296                 $res = true;
       
   297                 foreach ($ids as $id) {
       
   298                     $bool = $this->remove($id);
       
   299                     $res = $res && $bool;
       
   300                 }
       
   301                 return $res;
       
   302                 break;
       
   303             case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
       
   304                 $ids = $this->_slowBackend->getIdsNotMatchingTags($tags);
       
   305                 $res = true;
       
   306                 foreach ($ids as $id) {
       
   307                     $bool = $this->remove($id);
       
   308                     $res = $res && $bool;
       
   309                 }
       
   310                 return $res;
       
   311                 break;
       
   312             case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
       
   313                 $ids = $this->_slowBackend->getIdsMatchingAnyTags($tags);
       
   314                 $res = true;
       
   315                 foreach ($ids as $id) {
       
   316                     $bool = $this->remove($id);
       
   317                     $res = $res && $bool;
       
   318                 }
       
   319                 return $res;
       
   320                 break;
       
   321             default:
       
   322                 Zend_Cache::throwException('Invalid mode for clean() method');
       
   323                 break;
       
   324         }
       
   325     }
       
   326 
       
   327     /**
       
   328      * Return an array of stored cache ids
       
   329      *
       
   330      * @return array array of stored cache ids (string)
       
   331      */
       
   332     public function getIds()
       
   333     {
       
   334         return $this->_slowBackend->getIds();
       
   335     }
       
   336 
       
   337     /**
       
   338      * Return an array of stored tags
       
   339      *
       
   340      * @return array array of stored tags (string)
       
   341      */
       
   342     public function getTags()
       
   343     {
       
   344         return $this->_slowBackend->getTags();
       
   345     }
       
   346 
       
   347     /**
       
   348      * Return an array of stored cache ids which match given tags
       
   349      *
       
   350      * In case of multiple tags, a logical AND is made between tags
       
   351      *
       
   352      * @param array $tags array of tags
       
   353      * @return array array of matching cache ids (string)
       
   354      */
       
   355     public function getIdsMatchingTags($tags = array())
       
   356     {
       
   357         return $this->_slowBackend->getIdsMatchingTags($tags);
       
   358     }
       
   359 
       
   360     /**
       
   361      * Return an array of stored cache ids which don't match given tags
       
   362      *
       
   363      * In case of multiple tags, a logical OR is made between tags
       
   364      *
       
   365      * @param array $tags array of tags
       
   366      * @return array array of not matching cache ids (string)
       
   367      */
       
   368     public function getIdsNotMatchingTags($tags = array())
       
   369     {
       
   370         return $this->_slowBackend->getIdsNotMatchingTags($tags);
       
   371     }
       
   372 
       
   373     /**
       
   374      * Return an array of stored cache ids which match any given tags
       
   375      *
       
   376      * In case of multiple tags, a logical AND is made between tags
       
   377      *
       
   378      * @param array $tags array of tags
       
   379      * @return array array of any matching cache ids (string)
       
   380      */
       
   381     public function getIdsMatchingAnyTags($tags = array())
       
   382     {
       
   383         return $this->_slowBackend->getIdsMatchingAnyTags($tags);
       
   384     }
       
   385 
       
   386 
       
   387     /**
       
   388      * Return the filling percentage of the backend storage
       
   389      *
       
   390      * @return int integer between 0 and 100
       
   391      */
       
   392     public function getFillingPercentage()
       
   393     {
       
   394         return $this->_slowBackend->getFillingPercentage();
       
   395     }
       
   396 
       
   397     /**
       
   398      * Return an array of metadatas for the given cache id
       
   399      *
       
   400      * The array must include these keys :
       
   401      * - expire : the expire timestamp
       
   402      * - tags : a string array of tags
       
   403      * - mtime : timestamp of last modification time
       
   404      *
       
   405      * @param string $id cache id
       
   406      * @return array array of metadatas (false if the cache id is not found)
       
   407      */
       
   408     public function getMetadatas($id)
       
   409     {
       
   410         return $this->_slowBackend->getMetadatas($id);
       
   411     }
       
   412 
       
   413     /**
       
   414      * Give (if possible) an extra lifetime to the given cache id
       
   415      *
       
   416      * @param string $id cache id
       
   417      * @param int $extraLifetime
       
   418      * @return boolean true if ok
       
   419      */
       
   420     public function touch($id, $extraLifetime)
       
   421     {
       
   422         return $this->_slowBackend->touch($id, $extraLifetime);
       
   423     }
       
   424 
       
   425     /**
       
   426      * Return an associative array of capabilities (booleans) of the backend
       
   427      *
       
   428      * The array must include these keys :
       
   429      * - automatic_cleaning (is automating cleaning necessary)
       
   430      * - tags (are tags supported)
       
   431      * - expired_read (is it possible to read expired cache records
       
   432      *                 (for doNotTestCacheValidity option for example))
       
   433      * - priority does the backend deal with priority when saving
       
   434      * - infinite_lifetime (is infinite lifetime can work with this backend)
       
   435      * - get_list (is it possible to get the list of cache ids and the complete list of tags)
       
   436      *
       
   437      * @return array associative of with capabilities
       
   438      */
       
   439     public function getCapabilities()
       
   440     {
       
   441         $slowBackendCapabilities = $this->_slowBackend->getCapabilities();
       
   442         return array(
       
   443             'automatic_cleaning' => $slowBackendCapabilities['automatic_cleaning'],
       
   444             'tags' => $slowBackendCapabilities['tags'],
       
   445             'expired_read' => $slowBackendCapabilities['expired_read'],
       
   446             'priority' => $slowBackendCapabilities['priority'],
       
   447             'infinite_lifetime' => $slowBackendCapabilities['infinite_lifetime'],
       
   448             'get_list' => $slowBackendCapabilities['get_list']
       
   449         );
       
   450     }
       
   451 
       
   452     /**
       
   453      * Prepare a serialized array to store datas and metadatas informations
       
   454      *
       
   455      * @param string $data data to store
       
   456      * @param int $lifetime original lifetime
       
   457      * @param int $priority priority
       
   458      * @return string serialize array to store into cache
       
   459      */
       
   460     private function _prepareData($data, $lifetime, $priority)
       
   461     {
       
   462         $lt = $lifetime;
       
   463         if ($lt === null) {
       
   464             $lt = 9999999999;
       
   465         }
       
   466         return serialize(array(
       
   467             'data' => $data,
       
   468             'lifetime' => $lifetime,
       
   469             'expire' => time() + $lt,
       
   470             'priority' => $priority
       
   471         ));
       
   472     }
       
   473 
       
   474     /**
       
   475      * Compute and return the lifetime for the fast backend
       
   476      *
       
   477      * @param int $lifetime original lifetime
       
   478      * @param int $priority priority
       
   479      * @param int $maxLifetime maximum lifetime
       
   480      * @return int lifetime for the fast backend
       
   481      */
       
   482     private function _getFastLifetime($lifetime, $priority, $maxLifetime = null)
       
   483     {
       
   484         if ($lifetime === null) {
       
   485             // if lifetime is null, we have an infinite lifetime
       
   486             // we need to use arbitrary lifetimes
       
   487             $fastLifetime = (int) (2592000 / (11 - $priority));
       
   488         } else {
       
   489             $fastLifetime = (int) ($lifetime / (11 - $priority));
       
   490         }
       
   491         if (($maxLifetime !== null) && ($maxLifetime >= 0)) {
       
   492             if ($fastLifetime > $maxLifetime) {
       
   493                 return $maxLifetime;
       
   494             }
       
   495         }
       
   496         return $fastLifetime;
       
   497     }
       
   498 
       
   499     /**
       
   500      * PUBLIC METHOD FOR UNIT TESTING ONLY !
       
   501      *
       
   502      * Force a cache record to expire
       
   503      *
       
   504      * @param string $id cache id
       
   505      */
       
   506     public function ___expire($id)
       
   507     {
       
   508         $this->_fastBackend->remove($id);
       
   509         $this->_slowBackend->___expire($id);
       
   510     }
       
   511 
       
   512     private function _getFastFillingPercentage($mode)
       
   513     {
       
   514 
       
   515         if ($mode == 'saving') {
       
   516             // mode saving
       
   517             if ($this->_fastBackendFillingPercentage === null) {
       
   518                 $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
       
   519             } else {
       
   520                 $rand = rand(1, $this->_options['stats_update_factor']);
       
   521                 if ($rand == 1) {
       
   522                     // we force a refresh
       
   523                     $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
       
   524                 }
       
   525             }
       
   526         } else {
       
   527             // mode loading
       
   528             // we compute the percentage only if it's not available in cache
       
   529             if ($this->_fastBackendFillingPercentage === null) {
       
   530                 $this->_fastBackendFillingPercentage = $this->_fastBackend->getFillingPercentage();
       
   531             }
       
   532         }
       
   533         return $this->_fastBackendFillingPercentage;
       
   534     }
       
   535 
       
   536 }