web/lib/Zend/Cache/Backend/Libmemcached.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: Libmemcached.php 23220 2010-10-22 10:24:14Z mabe $
       
    21  */
       
    22 
       
    23 
       
    24 /**
       
    25  * @see Zend_Cache_Backend_Interface
       
    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 class Zend_Cache_Backend_Libmemcached extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
       
    42 {
       
    43     /**
       
    44      * Default Server Values
       
    45      */
       
    46     const DEFAULT_HOST = '127.0.0.1';
       
    47     const DEFAULT_PORT =  11211;
       
    48     const DEFAULT_WEIGHT  = 1;
       
    49 
       
    50     /**
       
    51      * Log message
       
    52      */
       
    53     const TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND = 'Zend_Cache_Backend_Libmemcached::clean() : tags are unsupported by the Libmemcached backend';
       
    54     const TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND =  'Zend_Cache_Backend_Libmemcached::save() : tags are unsupported by the Libmemcached backend';
       
    55 
       
    56     /**
       
    57      * Available options
       
    58      *
       
    59      * =====> (array) servers :
       
    60      * an array of memcached server ; each memcached server is described by an associative array :
       
    61      * 'host' => (string) : the name of the memcached server
       
    62      * 'port' => (int) : the port of the memcached server
       
    63      * 'weight' => (int) : number of buckets to create for this server which in turn control its
       
    64      *                     probability of it being selected. The probability is relative to the total
       
    65      *                     weight of all servers.
       
    66      * =====> (array) client :
       
    67      * an array of memcached client options ; the memcached client is described by an associative array :
       
    68      * @see http://php.net/manual/memcached.constants.php
       
    69      * - The option name can be the name of the constant without the prefix 'OPT_'
       
    70      *   or the integer value of this option constant
       
    71      *
       
    72      * @var array available options
       
    73      */
       
    74     protected $_options = array(
       
    75         'servers' => array(array(
       
    76             'host'   => self::DEFAULT_HOST,
       
    77             'port'   => self::DEFAULT_PORT,
       
    78             'weight' => self::DEFAULT_WEIGHT,
       
    79         )),
       
    80         'client' => array()
       
    81     );
       
    82 
       
    83     /**
       
    84      * Memcached object
       
    85      *
       
    86      * @var mixed memcached object
       
    87      */
       
    88     protected $_memcache = null;
       
    89 
       
    90     /**
       
    91      * Constructor
       
    92      *
       
    93      * @param array $options associative array of options
       
    94      * @throws Zend_Cache_Exception
       
    95      * @return void
       
    96      */
       
    97     public function __construct(array $options = array())
       
    98     {
       
    99         if (!extension_loaded('memcached')) {
       
   100             Zend_Cache::throwException('The memcached extension must be loaded for using this backend !');
       
   101         }
       
   102 
       
   103         // override default client options
       
   104         $this->_options['client'] = array(
       
   105             Memcached::OPT_DISTRIBUTION         => Memcached::DISTRIBUTION_CONSISTENT,
       
   106             Memcached::OPT_HASH                 => Memcached::HASH_MD5,
       
   107             Memcached::OPT_LIBKETAMA_COMPATIBLE => true,
       
   108         );
       
   109 
       
   110         parent::__construct($options);
       
   111 
       
   112         if (isset($this->_options['servers'])) {
       
   113             $value = $this->_options['servers'];
       
   114             if (isset($value['host'])) {
       
   115                 // in this case, $value seems to be a simple associative array (one server only)
       
   116                 $value = array(0 => $value); // let's transform it into a classical array of associative arrays
       
   117             }
       
   118             $this->setOption('servers', $value);
       
   119         }
       
   120         $this->_memcache = new Memcached;
       
   121 
       
   122         // setup memcached client options
       
   123         foreach ($this->_options['client'] as $name => $value) {
       
   124             $optId = null;
       
   125             if (is_int($name)) {
       
   126                 $optId = $name;
       
   127             } else {
       
   128                 $optConst = 'Memcached::OPT_' . strtoupper($name);
       
   129                 if (defined($optConst)) {
       
   130                     $optId = constant($optConst);
       
   131                 } else {
       
   132                     $this->_log("Unknown memcached client option '{$name}' ({$optConst})");
       
   133                 }
       
   134             }
       
   135             if ($optId) {
       
   136                 if (!$this->_memcache->setOption($optId, $value)) {
       
   137                     $this->_log("Setting memcached client option '{$optId}' failed");
       
   138                 }
       
   139             }
       
   140         }
       
   141 
       
   142         // setup memcached servers
       
   143         $servers = array();
       
   144         foreach ($this->_options['servers'] as $server) {
       
   145             if (!array_key_exists('port', $server)) {
       
   146                 $server['port'] = self::DEFAULT_PORT;
       
   147             }
       
   148             if (!array_key_exists('weight', $server)) {
       
   149                 $server['weight'] = self::DEFAULT_WEIGHT;
       
   150             }
       
   151 
       
   152             $servers[] = array($server['host'], $server['port'], $server['weight']);
       
   153         }
       
   154         $this->_memcache->addServers($servers);
       
   155     }
       
   156 
       
   157     /**
       
   158      * Test if a cache is available for the given id and (if yes) return it (false else)
       
   159      *
       
   160      * @param  string  $id                     Cache id
       
   161      * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
       
   162      * @return string|false cached datas
       
   163      */
       
   164     public function load($id, $doNotTestCacheValidity = false)
       
   165     {
       
   166         $tmp = $this->_memcache->get($id);
       
   167         if (isset($tmp[0])) {
       
   168             return $tmp[0];
       
   169         }
       
   170         return false;
       
   171     }
       
   172 
       
   173     /**
       
   174      * Test if a cache is available or not (for the given id)
       
   175      *
       
   176      * @param  string $id Cache id
       
   177      * @return int|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
       
   178      */
       
   179     public function test($id)
       
   180     {
       
   181         $tmp = $this->_memcache->get($id);
       
   182         if (isset($tmp[0], $tmp[1])) {
       
   183             return (int)$tmp[1];
       
   184         }
       
   185         return false;
       
   186     }
       
   187 
       
   188     /**
       
   189      * Save some string datas into a cache record
       
   190      *
       
   191      * Note : $data is always "string" (serialization is done by the
       
   192      * core not by the backend)
       
   193      *
       
   194      * @param  string $data             Datas to cache
       
   195      * @param  string $id               Cache id
       
   196      * @param  array  $tags             Array of strings, the cache record will be tagged by each string entry
       
   197      * @param  int    $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
       
   198      * @return boolean True if no problem
       
   199      */
       
   200     public function save($data, $id, $tags = array(), $specificLifetime = false)
       
   201     {
       
   202         $lifetime = $this->getLifetime($specificLifetime);
       
   203 
       
   204         // ZF-8856: using set because add needs a second request if item already exists
       
   205         $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $lifetime);
       
   206         if ($result === false) {
       
   207             $rsCode = $this->_memcache->getResultCode();
       
   208             $rsMsg  = $this->_memcache->getResultMessage();
       
   209             $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}");
       
   210         }
       
   211 
       
   212         if (count($tags) > 0) {
       
   213             $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
       
   214         }
       
   215 
       
   216         return $result;
       
   217     }
       
   218 
       
   219     /**
       
   220      * Remove a cache record
       
   221      *
       
   222      * @param  string $id Cache id
       
   223      * @return boolean True if no problem
       
   224      */
       
   225     public function remove($id)
       
   226     {
       
   227         return $this->_memcache->delete($id);
       
   228     }
       
   229 
       
   230     /**
       
   231      * Clean some cache records
       
   232      *
       
   233      * Available modes are :
       
   234      * 'all' (default)  => remove all cache entries ($tags is not used)
       
   235      * 'old'            => unsupported
       
   236      * 'matchingTag'    => unsupported
       
   237      * 'notMatchingTag' => unsupported
       
   238      * 'matchingAnyTag' => unsupported
       
   239      *
       
   240      * @param  string $mode Clean mode
       
   241      * @param  array  $tags Array of tags
       
   242      * @throws Zend_Cache_Exception
       
   243      * @return boolean True if no problem
       
   244      */
       
   245     public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
       
   246     {
       
   247         switch ($mode) {
       
   248             case Zend_Cache::CLEANING_MODE_ALL:
       
   249                 return $this->_memcache->flush();
       
   250                 break;
       
   251             case Zend_Cache::CLEANING_MODE_OLD:
       
   252                 $this->_log("Zend_Cache_Backend_Libmemcached::clean() : CLEANING_MODE_OLD is unsupported by the Libmemcached backend");
       
   253                 break;
       
   254             case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
       
   255             case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
       
   256             case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
       
   257                 $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_LIBMEMCACHED_BACKEND);
       
   258                 break;
       
   259                default:
       
   260                 Zend_Cache::throwException('Invalid mode for clean() method');
       
   261                    break;
       
   262         }
       
   263     }
       
   264 
       
   265     /**
       
   266      * Return true if the automatic cleaning is available for the backend
       
   267      *
       
   268      * @return boolean
       
   269      */
       
   270     public function isAutomaticCleaningAvailable()
       
   271     {
       
   272         return false;
       
   273     }
       
   274 
       
   275     /**
       
   276      * Set the frontend directives
       
   277      *
       
   278      * @param  array $directives Assoc of directives
       
   279      * @throws Zend_Cache_Exception
       
   280      * @return void
       
   281      */
       
   282     public function setDirectives($directives)
       
   283     {
       
   284         parent::setDirectives($directives);
       
   285         $lifetime = $this->getLifetime(false);
       
   286         if ($lifetime > 2592000) {
       
   287             // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds)
       
   288             $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime');
       
   289         }
       
   290         if ($lifetime === null) {
       
   291             // #ZF-4614 : we tranform null to zero to get the maximal lifetime
       
   292             parent::setDirectives(array('lifetime' => 0));
       
   293         }
       
   294     }
       
   295 
       
   296     /**
       
   297      * Return an array of stored cache ids
       
   298      *
       
   299      * @return array array of stored cache ids (string)
       
   300      */
       
   301     public function getIds()
       
   302     {
       
   303         $this->_log("Zend_Cache_Backend_Libmemcached::save() : getting the list of cache ids is unsupported by the Libmemcached backend");
       
   304         return array();
       
   305     }
       
   306 
       
   307     /**
       
   308      * Return an array of stored tags
       
   309      *
       
   310      * @return array array of stored tags (string)
       
   311      */
       
   312     public function getTags()
       
   313     {
       
   314         $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
       
   315         return array();
       
   316     }
       
   317 
       
   318     /**
       
   319      * Return an array of stored cache ids which match given tags
       
   320      *
       
   321      * In case of multiple tags, a logical AND is made between tags
       
   322      *
       
   323      * @param array $tags array of tags
       
   324      * @return array array of matching cache ids (string)
       
   325      */
       
   326     public function getIdsMatchingTags($tags = array())
       
   327     {
       
   328         $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
       
   329         return array();
       
   330     }
       
   331 
       
   332     /**
       
   333      * Return an array of stored cache ids which don't match given tags
       
   334      *
       
   335      * In case of multiple tags, a logical OR is made between tags
       
   336      *
       
   337      * @param array $tags array of tags
       
   338      * @return array array of not matching cache ids (string)
       
   339      */
       
   340     public function getIdsNotMatchingTags($tags = array())
       
   341     {
       
   342         $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
       
   343         return array();
       
   344     }
       
   345 
       
   346     /**
       
   347      * Return an array of stored cache ids which match any given tags
       
   348      *
       
   349      * In case of multiple tags, a logical AND is made between tags
       
   350      *
       
   351      * @param array $tags array of tags
       
   352      * @return array array of any matching cache ids (string)
       
   353      */
       
   354     public function getIdsMatchingAnyTags($tags = array())
       
   355     {
       
   356         $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_LIBMEMCACHED_BACKEND);
       
   357         return array();
       
   358     }
       
   359 
       
   360     /**
       
   361      * Return the filling percentage of the backend storage
       
   362      *
       
   363      * @throws Zend_Cache_Exception
       
   364      * @return int integer between 0 and 100
       
   365      */
       
   366     public function getFillingPercentage()
       
   367     {
       
   368         $mems = $this->_memcache->getStats();
       
   369         if ($mems === false) {
       
   370             return 0;
       
   371         }
       
   372 
       
   373         $memSize = null;
       
   374         $memUsed = null;
       
   375         foreach ($mems as $key => $mem) {
       
   376             if ($mem === false) {
       
   377                 $this->_log('can\'t get stat from ' . $key);
       
   378                 continue;
       
   379             }
       
   380 
       
   381             $eachSize = $mem['limit_maxbytes'];
       
   382             $eachUsed = $mem['bytes'];
       
   383             if ($eachUsed > $eachSize) {
       
   384                 $eachUsed = $eachSize;
       
   385             }
       
   386 
       
   387             $memSize += $eachSize;
       
   388             $memUsed += $eachUsed;
       
   389         }
       
   390 
       
   391         if ($memSize === null || $memUsed === null) {
       
   392             Zend_Cache::throwException('Can\'t get filling percentage');
       
   393         }
       
   394 
       
   395         return ((int) (100. * ($memUsed / $memSize)));
       
   396     }
       
   397 
       
   398     /**
       
   399      * Return an array of metadatas for the given cache id
       
   400      *
       
   401      * The array must include these keys :
       
   402      * - expire : the expire timestamp
       
   403      * - tags : a string array of tags
       
   404      * - mtime : timestamp of last modification time
       
   405      *
       
   406      * @param string $id cache id
       
   407      * @return array array of metadatas (false if the cache id is not found)
       
   408      */
       
   409     public function getMetadatas($id)
       
   410     {
       
   411         $tmp = $this->_memcache->get($id);
       
   412         if (isset($tmp[0], $tmp[1], $tmp[2])) {
       
   413             $data     = $tmp[0];
       
   414             $mtime    = $tmp[1];
       
   415             $lifetime = $tmp[2];
       
   416             return array(
       
   417                 'expire' => $mtime + $lifetime,
       
   418                 'tags' => array(),
       
   419                 'mtime' => $mtime
       
   420             );
       
   421         }
       
   422 
       
   423         return false;
       
   424     }
       
   425 
       
   426     /**
       
   427      * Give (if possible) an extra lifetime to the given cache id
       
   428      *
       
   429      * @param string $id cache id
       
   430      * @param int $extraLifetime
       
   431      * @return boolean true if ok
       
   432      */
       
   433     public function touch($id, $extraLifetime)
       
   434     {
       
   435         $tmp = $this->_memcache->get($id);
       
   436         if (isset($tmp[0], $tmp[1], $tmp[2])) {
       
   437             $data     = $tmp[0];
       
   438             $mtime    = $tmp[1];
       
   439             $lifetime = $tmp[2];
       
   440             $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime;
       
   441             if ($newLifetime <=0) {
       
   442                 return false;
       
   443             }
       
   444             // #ZF-5702 : we try replace() first becase set() seems to be slower
       
   445             if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $newLifetime))) {
       
   446                 $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $newLifetime);
       
   447                 if ($result === false) {
       
   448                     $rsCode = $this->_memcache->getResultCode();
       
   449                     $rsMsg  = $this->_memcache->getResultMessage();
       
   450                     $this->_log("Memcached::set() failed: [{$rsCode}] {$rsMsg}");
       
   451                 }
       
   452             }
       
   453             return $result;
       
   454         }
       
   455         return false;
       
   456     }
       
   457 
       
   458     /**
       
   459      * Return an associative array of capabilities (booleans) of the backend
       
   460      *
       
   461      * The array must include these keys :
       
   462      * - automatic_cleaning (is automating cleaning necessary)
       
   463      * - tags (are tags supported)
       
   464      * - expired_read (is it possible to read expired cache records
       
   465      *                 (for doNotTestCacheValidity option for example))
       
   466      * - priority does the backend deal with priority when saving
       
   467      * - infinite_lifetime (is infinite lifetime can work with this backend)
       
   468      * - get_list (is it possible to get the list of cache ids and the complete list of tags)
       
   469      *
       
   470      * @return array associative of with capabilities
       
   471      */
       
   472     public function getCapabilities()
       
   473     {
       
   474         return array(
       
   475             'automatic_cleaning' => false,
       
   476             'tags' => false,
       
   477             'expired_read' => false,
       
   478             'priority' => false,
       
   479             'infinite_lifetime' => false,
       
   480             'get_list' => false
       
   481         );
       
   482     }
       
   483 
       
   484 }