File indexing completed on 2025-01-19 05:20:57

0001 <?php
0002 /**
0003  * Zend Framework
0004  *
0005  * LICENSE
0006  *
0007  * This source file is subject to the new BSD license that is bundled
0008  * with this package in the file LICENSE.txt.
0009  * It is also available through the world-wide-web at this URL:
0010  * http://framework.zend.com/license/new-bsd
0011  * If you did not receive a copy of the license and are unable to
0012  * obtain it through the world-wide-web, please send an email
0013  * to license@zend.com so we can send you a copy immediately.
0014  *
0015  * @category   Zend
0016  * @package    Zend_Cache
0017  * @subpackage Zend_Cache_Backend
0018  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0019  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0020  * @version    $Id$
0021  */
0022 
0023 
0024 /**
0025  * @see Zend_Cache_Backend_Interface
0026  */
0027 // require_once 'Zend/Cache/Backend/ExtendedInterface.php';
0028 
0029 /**
0030  * @see Zend_Cache_Backend
0031  */
0032 // require_once 'Zend/Cache/Backend.php';
0033 
0034 
0035 /**
0036  * @package    Zend_Cache
0037  * @subpackage Zend_Cache_Backend
0038  * @copyright  Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
0039  * @license    http://framework.zend.com/license/new-bsd     New BSD License
0040  */
0041 class Zend_Cache_Backend_Memcached extends Zend_Cache_Backend implements Zend_Cache_Backend_ExtendedInterface
0042 {
0043     /**
0044      * Default Values
0045      */
0046     const DEFAULT_HOST = '127.0.0.1';
0047     const DEFAULT_PORT =  11211;
0048     const DEFAULT_PERSISTENT = true;
0049     const DEFAULT_WEIGHT  = 1;
0050     const DEFAULT_TIMEOUT = 1;
0051     const DEFAULT_RETRY_INTERVAL = 15;
0052     const DEFAULT_STATUS = true;
0053     const DEFAULT_FAILURE_CALLBACK = null;
0054 
0055     /**
0056      * Log message
0057      */
0058     const TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND = 'Zend_Cache_Backend_Memcached::clean() : tags are unsupported by the Memcached backend';
0059     const TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND =  'Zend_Cache_Backend_Memcached::save() : tags are unsupported by the Memcached backend';
0060 
0061     /**
0062      * Available options
0063      *
0064      * =====> (array) servers :
0065      * an array of memcached server ; each memcached server is described by an associative array :
0066      * 'host' => (string) : the name of the memcached server
0067      * 'port' => (int) : the port of the memcached server
0068      * 'persistent' => (bool) : use or not persistent connections to this memcached server
0069      * 'weight' => (int) : number of buckets to create for this server which in turn control its
0070      *                     probability of it being selected. The probability is relative to the total
0071      *                     weight of all servers.
0072      * 'timeout' => (int) : value in seconds which will be used for connecting to the daemon. Think twice
0073      *                      before changing the default value of 1 second - you can lose all the
0074      *                      advantages of caching if your connection is too slow.
0075      * 'retry_interval' => (int) : controls how often a failed server will be retried, the default value
0076      *                             is 15 seconds. Setting this parameter to -1 disables automatic retry.
0077      * 'status' => (bool) : controls if the server should be flagged as online.
0078      * 'failure_callback' => (callback) : Allows the user to specify a callback function to run upon
0079      *                                    encountering an error. The callback is run before failover
0080      *                                    is attempted. The function takes two parameters, the hostname
0081      *                                    and port of the failed server.
0082      *
0083      * =====> (boolean) compression :
0084      * true if you want to use on-the-fly compression
0085      *
0086      * =====> (boolean) compatibility :
0087      * true if you use old memcache server or extension
0088      *
0089      * @var array available options
0090      */
0091     protected $_options = array(
0092         'servers' => array(array(
0093             'host' => self::DEFAULT_HOST,
0094             'port' => self::DEFAULT_PORT,
0095             'persistent' => self::DEFAULT_PERSISTENT,
0096             'weight'  => self::DEFAULT_WEIGHT,
0097             'timeout' => self::DEFAULT_TIMEOUT,
0098             'retry_interval' => self::DEFAULT_RETRY_INTERVAL,
0099             'status' => self::DEFAULT_STATUS,
0100             'failure_callback' => self::DEFAULT_FAILURE_CALLBACK
0101         )),
0102         'compression' => false,
0103         'compatibility' => false,
0104     );
0105 
0106     /**
0107      * Memcache object
0108      *
0109      * @var mixed memcache object
0110      */
0111     protected $_memcache = null;
0112 
0113     /**
0114      * Constructor
0115      *
0116      * @param array $options associative array of options
0117      * @throws Zend_Cache_Exception
0118      * @return void
0119      */
0120     public function __construct(array $options = array())
0121     {
0122         if (!extension_loaded('memcache')) {
0123             Zend_Cache::throwException('The memcache extension must be loaded for using this backend !');
0124         }
0125         parent::__construct($options);
0126         if (isset($this->_options['servers'])) {
0127             $value= $this->_options['servers'];
0128             if (isset($value['host'])) {
0129                 // in this case, $value seems to be a simple associative array (one server only)
0130                 $value = array(0 => $value); // let's transform it into a classical array of associative arrays
0131             }
0132             $this->setOption('servers', $value);
0133         }
0134         $this->_memcache = new Memcache;
0135         foreach ($this->_options['servers'] as $server) {
0136             if (!array_key_exists('port', $server)) {
0137                 $server['port'] = self::DEFAULT_PORT;
0138             }
0139             if (!array_key_exists('persistent', $server)) {
0140                 $server['persistent'] = self::DEFAULT_PERSISTENT;
0141             }
0142             if (!array_key_exists('weight', $server)) {
0143                 $server['weight'] = self::DEFAULT_WEIGHT;
0144             }
0145             if (!array_key_exists('timeout', $server)) {
0146                 $server['timeout'] = self::DEFAULT_TIMEOUT;
0147             }
0148             if (!array_key_exists('retry_interval', $server)) {
0149                 $server['retry_interval'] = self::DEFAULT_RETRY_INTERVAL;
0150             }
0151             if (!array_key_exists('status', $server)) {
0152                 $server['status'] = self::DEFAULT_STATUS;
0153             }
0154             if (!array_key_exists('failure_callback', $server)) {
0155                 $server['failure_callback'] = self::DEFAULT_FAILURE_CALLBACK;
0156             }
0157             if ($this->_options['compatibility']) {
0158                 // No status for compatibility mode (#ZF-5887)
0159                 $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'],
0160                                         $server['weight'], $server['timeout'],
0161                                         $server['retry_interval']);
0162             } else {
0163                 $this->_memcache->addServer($server['host'], $server['port'], $server['persistent'],
0164                                         $server['weight'], $server['timeout'],
0165                                         $server['retry_interval'],
0166                                         $server['status'], $server['failure_callback']);
0167             }
0168         }
0169     }
0170 
0171     /**
0172      * Test if a cache is available for the given id and (if yes) return it (false else)
0173      *
0174      * @param  string  $id                     Cache id
0175      * @param  boolean $doNotTestCacheValidity If set to true, the cache validity won't be tested
0176      * @return string|false cached datas
0177      */
0178     public function load($id, $doNotTestCacheValidity = false)
0179     {
0180         $tmp = $this->_memcache->get($id);
0181         if (is_array($tmp) && isset($tmp[0])) {
0182             return $tmp[0];
0183         }
0184         return false;
0185     }
0186 
0187     /**
0188      * Test if a cache is available or not (for the given id)
0189      *
0190      * @param  string $id Cache id
0191      * @return mixed|false (a cache is not available) or "last modified" timestamp (int) of the available cache record
0192      */
0193     public function test($id)
0194     {
0195         $tmp = $this->_memcache->get($id);
0196         if (is_array($tmp)) {
0197             return $tmp[1];
0198         }
0199         return false;
0200     }
0201 
0202     /**
0203      * Save some string datas into a cache record
0204      *
0205      * Note : $data is always "string" (serialization is done by the
0206      * core not by the backend)
0207      *
0208      * @param  string $data             Datas to cache
0209      * @param  string $id               Cache id
0210      * @param  array  $tags             Array of strings, the cache record will be tagged by each string entry
0211      * @param  int    $specificLifetime If != false, set a specific lifetime for this cache record (null => infinite lifetime)
0212      * @return boolean True if no problem
0213      */
0214     public function save($data, $id, $tags = array(), $specificLifetime = false)
0215     {
0216         $lifetime = $this->getLifetime($specificLifetime);
0217         if ($this->_options['compression']) {
0218             $flag = MEMCACHE_COMPRESSED;
0219         } else {
0220             $flag = 0;
0221         }
0222 
0223         // ZF-8856: using set because add needs a second request if item already exists
0224         $result = @$this->_memcache->set($id, array($data, time(), $lifetime), $flag, $lifetime);
0225 
0226         if (count($tags) > 0) {
0227             $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
0228         }
0229 
0230         return $result;
0231     }
0232 
0233     /**
0234      * Remove a cache record
0235      *
0236      * @param  string $id Cache id
0237      * @return boolean True if no problem
0238      */
0239     public function remove($id)
0240     {
0241         return $this->_memcache->delete($id, 0);
0242     }
0243 
0244     /**
0245      * Clean some cache records
0246      *
0247      * Available modes are :
0248      * 'all' (default)  => remove all cache entries ($tags is not used)
0249      * 'old'            => unsupported
0250      * 'matchingTag'    => unsupported
0251      * 'notMatchingTag' => unsupported
0252      * 'matchingAnyTag' => unsupported
0253      *
0254      * @param  string $mode Clean mode
0255      * @param  array  $tags Array of tags
0256      * @throws Zend_Cache_Exception
0257      * @return boolean True if no problem
0258      */
0259     public function clean($mode = Zend_Cache::CLEANING_MODE_ALL, $tags = array())
0260     {
0261         switch ($mode) {
0262             case Zend_Cache::CLEANING_MODE_ALL:
0263                 return $this->_memcache->flush();
0264                 break;
0265             case Zend_Cache::CLEANING_MODE_OLD:
0266                 $this->_log("Zend_Cache_Backend_Memcached::clean() : CLEANING_MODE_OLD is unsupported by the Memcached backend");
0267                 break;
0268             case Zend_Cache::CLEANING_MODE_MATCHING_TAG:
0269             case Zend_Cache::CLEANING_MODE_NOT_MATCHING_TAG:
0270             case Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG:
0271                 $this->_log(self::TAGS_UNSUPPORTED_BY_CLEAN_OF_MEMCACHED_BACKEND);
0272                 break;
0273                default:
0274                 Zend_Cache::throwException('Invalid mode for clean() method');
0275                    break;
0276         }
0277     }
0278 
0279     /**
0280      * Return true if the automatic cleaning is available for the backend
0281      *
0282      * @return boolean
0283      */
0284     public function isAutomaticCleaningAvailable()
0285     {
0286         return false;
0287     }
0288 
0289     /**
0290      * Set the frontend directives
0291      *
0292      * @param  array $directives Assoc of directives
0293      * @throws Zend_Cache_Exception
0294      * @return void
0295      */
0296     public function setDirectives($directives)
0297     {
0298         parent::setDirectives($directives);
0299         $lifetime = $this->getLifetime(false);
0300         if ($lifetime > 2592000) {
0301             // #ZF-3490 : For the memcached backend, there is a lifetime limit of 30 days (2592000 seconds)
0302             $this->_log('memcached backend has a limit of 30 days (2592000 seconds) for the lifetime');
0303         }
0304         if ($lifetime === null) {
0305             // #ZF-4614 : we tranform null to zero to get the maximal lifetime
0306             parent::setDirectives(array('lifetime' => 0));
0307         }
0308     }
0309 
0310     /**
0311      * Return an array of stored cache ids
0312      *
0313      * @return array array of stored cache ids (string)
0314      */
0315     public function getIds()
0316     {
0317         $this->_log("Zend_Cache_Backend_Memcached::save() : getting the list of cache ids is unsupported by the Memcache backend");
0318         return array();
0319     }
0320 
0321     /**
0322      * Return an array of stored tags
0323      *
0324      * @return array array of stored tags (string)
0325      */
0326     public function getTags()
0327     {
0328         $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
0329         return array();
0330     }
0331 
0332     /**
0333      * Return an array of stored cache ids which match given tags
0334      *
0335      * In case of multiple tags, a logical AND is made between tags
0336      *
0337      * @param array $tags array of tags
0338      * @return array array of matching cache ids (string)
0339      */
0340     public function getIdsMatchingTags($tags = array())
0341     {
0342         $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
0343         return array();
0344     }
0345 
0346     /**
0347      * Return an array of stored cache ids which don't match given tags
0348      *
0349      * In case of multiple tags, a logical OR is made between tags
0350      *
0351      * @param array $tags array of tags
0352      * @return array array of not matching cache ids (string)
0353      */
0354     public function getIdsNotMatchingTags($tags = array())
0355     {
0356         $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
0357         return array();
0358     }
0359 
0360     /**
0361      * Return an array of stored cache ids which match any given tags
0362      *
0363      * In case of multiple tags, a logical AND is made between tags
0364      *
0365      * @param array $tags array of tags
0366      * @return array array of any matching cache ids (string)
0367      */
0368     public function getIdsMatchingAnyTags($tags = array())
0369     {
0370         $this->_log(self::TAGS_UNSUPPORTED_BY_SAVE_OF_MEMCACHED_BACKEND);
0371         return array();
0372     }
0373 
0374     /**
0375      * Return the filling percentage of the backend storage
0376      *
0377      * @throws Zend_Cache_Exception
0378      * @return int integer between 0 and 100
0379      */
0380     public function getFillingPercentage()
0381     {
0382         $mems = $this->_memcache->getExtendedStats();
0383 
0384         $memSize = null;
0385         $memUsed = null;
0386         foreach ($mems as $key => $mem) {
0387             if ($mem === false) {
0388                 $this->_log('can\'t get stat from ' . $key);
0389                 continue;
0390             }
0391 
0392             $eachSize = $mem['limit_maxbytes'];
0393 
0394             /**
0395              * Couchbase 1.x uses 'mem_used' instead of 'bytes'
0396              * @see https://www.couchbase.com/issues/browse/MB-3466
0397              */
0398             $eachUsed = isset($mem['bytes']) ? $mem['bytes'] : $mem['mem_used'];
0399             if ($eachUsed > $eachSize) {
0400                 $eachUsed = $eachSize;
0401             }
0402 
0403             $memSize += $eachSize;
0404             $memUsed += $eachUsed;
0405         }
0406 
0407         if ($memSize === null || $memUsed === null) {
0408             Zend_Cache::throwException('Can\'t get filling percentage');
0409         }
0410 
0411         return ((int) (100. * ($memUsed / $memSize)));
0412     }
0413 
0414     /**
0415      * Return an array of metadatas for the given cache id
0416      *
0417      * The array must include these keys :
0418      * - expire : the expire timestamp
0419      * - tags : a string array of tags
0420      * - mtime : timestamp of last modification time
0421      *
0422      * @param string $id cache id
0423      * @return array array of metadatas (false if the cache id is not found)
0424      */
0425     public function getMetadatas($id)
0426     {
0427         $tmp = $this->_memcache->get($id);
0428         if (is_array($tmp)) {
0429             $data = $tmp[0];
0430             $mtime = $tmp[1];
0431             if (!isset($tmp[2])) {
0432                 // because this record is only with 1.7 release
0433                 // if old cache records are still there...
0434                 return false;
0435             }
0436             $lifetime = $tmp[2];
0437             return array(
0438                 'expire' => $mtime + $lifetime,
0439                 'tags' => array(),
0440                 'mtime' => $mtime
0441             );
0442         }
0443         return false;
0444     }
0445 
0446     /**
0447      * Give (if possible) an extra lifetime to the given cache id
0448      *
0449      * @param string $id cache id
0450      * @param int $extraLifetime
0451      * @return boolean true if ok
0452      */
0453     public function touch($id, $extraLifetime)
0454     {
0455         if ($this->_options['compression']) {
0456             $flag = MEMCACHE_COMPRESSED;
0457         } else {
0458             $flag = 0;
0459         }
0460         $tmp = $this->_memcache->get($id);
0461         if (is_array($tmp)) {
0462             $data = $tmp[0];
0463             $mtime = $tmp[1];
0464             if (!isset($tmp[2])) {
0465                 // because this record is only with 1.7 release
0466                 // if old cache records are still there...
0467                 return false;
0468             }
0469             $lifetime = $tmp[2];
0470             $newLifetime = $lifetime - (time() - $mtime) + $extraLifetime;
0471             if ($newLifetime <=0) {
0472                 return false;
0473             }
0474             // #ZF-5702 : we try replace() first becase set() seems to be slower
0475             if (!($result = $this->_memcache->replace($id, array($data, time(), $newLifetime), $flag, $newLifetime))) {
0476                 $result = $this->_memcache->set($id, array($data, time(), $newLifetime), $flag, $newLifetime);
0477             }
0478             return $result;
0479         }
0480         return false;
0481     }
0482 
0483     /**
0484      * Return an associative array of capabilities (booleans) of the backend
0485      *
0486      * The array must include these keys :
0487      * - automatic_cleaning (is automating cleaning necessary)
0488      * - tags (are tags supported)
0489      * - expired_read (is it possible to read expired cache records
0490      *                 (for doNotTestCacheValidity option for example))
0491      * - priority does the backend deal with priority when saving
0492      * - infinite_lifetime (is infinite lifetime can work with this backend)
0493      * - get_list (is it possible to get the list of cache ids and the complete list of tags)
0494      *
0495      * @return array associative of with capabilities
0496      */
0497     public function getCapabilities()
0498     {
0499         return array(
0500             'automatic_cleaning' => false,
0501             'tags' => false,
0502             'expired_read' => false,
0503             'priority' => false,
0504             'infinite_lifetime' => false,
0505             'get_list' => false
0506         );
0507     }
0508 
0509 }