PageRenderTime 54ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/Cache/Backend/Libmemcached.php

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