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