MemcacheEngine.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. <?php
  2. /**
  3. * Memcache storage engine for cache
  4. *
  5. *
  6. * PHP 5
  7. *
  8. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  9. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  10. *
  11. * Licensed under The MIT License
  12. * For full copyright and license information, please see the LICENSE.txt
  13. * Redistributions of files must retain the above copyright notice.
  14. *
  15. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  16. * @link http://cakephp.org CakePHP(tm) Project
  17. * @package Cake.Cache.Engine
  18. * @since CakePHP(tm) v 1.2.0.4933
  19. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  20. */
  21. /**
  22. * Memcache storage engine for cache. Memcache has some limitations in the amount of
  23. * control you have over expire times far in the future. See MemcacheEngine::write() for
  24. * more information.
  25. *
  26. * @package Cake.Cache.Engine
  27. */
  28. class MemcacheEngine extends CacheEngine {
  29. /**
  30. * Contains the compiled group names
  31. * (prefixed with the global configuration prefix)
  32. *
  33. * @var array
  34. */
  35. protected $_compiledGroupNames = array();
  36. /**
  37. * Memcache wrapper.
  38. *
  39. * @var Memcache
  40. */
  41. protected $_Memcache = null;
  42. /**
  43. * Settings
  44. *
  45. * - servers = string or array of memcache servers, default => 127.0.0.1. If an
  46. * array MemcacheEngine will use them as a pool.
  47. * - compress = boolean, default => false
  48. *
  49. * @var array
  50. */
  51. public $settings = array();
  52. /**
  53. * Initialize the Cache Engine
  54. *
  55. * Called automatically by the cache frontend
  56. * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
  57. *
  58. * @param array $settings array of setting for the engine
  59. * @return boolean True if the engine has been successfully initialized, false if not
  60. */
  61. public function init($settings = array()) {
  62. if (!class_exists('Memcache')) {
  63. return false;
  64. }
  65. if (!isset($settings['prefix'])) {
  66. $settings['prefix'] = Inflector::slug(APP_DIR) . '_';
  67. }
  68. $settings += array(
  69. 'engine' => 'Memcache',
  70. 'servers' => array('127.0.0.1'),
  71. 'compress' => false,
  72. 'persistent' => true
  73. );
  74. parent::init($settings);
  75. if ($this->settings['compress']) {
  76. $this->settings['compress'] = MEMCACHE_COMPRESSED;
  77. }
  78. if (is_string($this->settings['servers'])) {
  79. $this->settings['servers'] = array($this->settings['servers']);
  80. }
  81. if (!isset($this->_Memcache)) {
  82. $return = false;
  83. $this->_Memcache = new Memcache();
  84. foreach ($this->settings['servers'] as $server) {
  85. list($host, $port) = $this->_parseServerString($server);
  86. if ($this->_Memcache->addServer($host, $port, $this->settings['persistent'])) {
  87. $return = true;
  88. }
  89. }
  90. return $return;
  91. }
  92. return true;
  93. }
  94. /**
  95. * Parses the server address into the host/port. Handles both IPv6 and IPv4
  96. * addresses and Unix sockets
  97. *
  98. * @param string $server The server address string.
  99. * @return array Array containing host, port
  100. */
  101. protected function _parseServerString($server) {
  102. if ($server[0] === 'u') {
  103. return array($server, 0);
  104. }
  105. if (substr($server, 0, 1) === '[') {
  106. $position = strpos($server, ']:');
  107. if ($position !== false) {
  108. $position++;
  109. }
  110. } else {
  111. $position = strpos($server, ':');
  112. }
  113. $port = 11211;
  114. $host = $server;
  115. if ($position !== false) {
  116. $host = substr($server, 0, $position);
  117. $port = substr($server, $position + 1);
  118. }
  119. return array($host, $port);
  120. }
  121. /**
  122. * Write data for key into cache. When using memcache as your cache engine
  123. * remember that the Memcache pecl extension does not support cache expiry times greater
  124. * than 30 days in the future. Any duration greater than 30 days will be treated as never expiring.
  125. *
  126. * @param string $key Identifier for the data
  127. * @param mixed $value Data to be cached
  128. * @param integer $duration How long to cache the data, in seconds
  129. * @return boolean True if the data was successfully cached, false on failure
  130. * @see http://php.net/manual/en/memcache.set.php
  131. */
  132. public function write($key, $value, $duration) {
  133. if ($duration > 30 * DAY) {
  134. $duration = 0;
  135. }
  136. return $this->_Memcache->set($key, $value, $this->settings['compress'], $duration);
  137. }
  138. /**
  139. * Read a key from the cache
  140. *
  141. * @param string $key Identifier for the data
  142. * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
  143. */
  144. public function read($key) {
  145. return $this->_Memcache->get($key);
  146. }
  147. /**
  148. * Increments the value of an integer cached key
  149. *
  150. * @param string $key Identifier for the data
  151. * @param integer $offset How much to increment
  152. * @return New incremented value, false otherwise
  153. * @throws CacheException when you try to increment with compress = true
  154. */
  155. public function increment($key, $offset = 1) {
  156. if ($this->settings['compress']) {
  157. throw new CacheException(
  158. __d('cake_dev', 'Method %s not implemented for compressed cache in %s', 'increment()', __CLASS__)
  159. );
  160. }
  161. return $this->_Memcache->increment($key, $offset);
  162. }
  163. /**
  164. * Decrements the value of an integer cached key
  165. *
  166. * @param string $key Identifier for the data
  167. * @param integer $offset How much to subtract
  168. * @return New decremented value, false otherwise
  169. * @throws CacheException when you try to decrement with compress = true
  170. */
  171. public function decrement($key, $offset = 1) {
  172. if ($this->settings['compress']) {
  173. throw new CacheException(
  174. __d('cake_dev', 'Method %s not implemented for compressed cache in %s', 'decrement()', __CLASS__)
  175. );
  176. }
  177. return $this->_Memcache->decrement($key, $offset);
  178. }
  179. /**
  180. * Delete a key from the cache
  181. *
  182. * @param string $key Identifier for the data
  183. * @return boolean True if the value was successfully deleted, false if it didn't exist or couldn't be removed
  184. */
  185. public function delete($key) {
  186. return $this->_Memcache->delete($key);
  187. }
  188. /**
  189. * Delete all keys from the cache
  190. *
  191. * @param boolean $check
  192. * @return boolean True if the cache was successfully cleared, false otherwise
  193. */
  194. public function clear($check) {
  195. if ($check) {
  196. return true;
  197. }
  198. foreach ($this->_Memcache->getExtendedStats('slabs') as $slabs) {
  199. foreach (array_keys($slabs) as $slabId) {
  200. if (!is_numeric($slabId)) {
  201. continue;
  202. }
  203. foreach ($this->_Memcache->getExtendedStats('cachedump', $slabId) as $stats) {
  204. if (!is_array($stats)) {
  205. continue;
  206. }
  207. foreach (array_keys($stats) as $key) {
  208. if (strpos($key, $this->settings['prefix']) === 0) {
  209. $this->_Memcache->delete($key);
  210. }
  211. }
  212. }
  213. }
  214. }
  215. return true;
  216. }
  217. /**
  218. * Connects to a server in connection pool
  219. *
  220. * @param string $host host ip address or name
  221. * @param integer $port Server port
  222. * @return boolean True if memcache server was connected
  223. */
  224. public function connect($host, $port = 11211) {
  225. if ($this->_Memcache->getServerStatus($host, $port) === 0) {
  226. if ($this->_Memcache->connect($host, $port)) {
  227. return true;
  228. }
  229. return false;
  230. }
  231. return true;
  232. }
  233. /**
  234. * Returns the `group value` for each of the configured groups
  235. * If the group initial value was not found, then it initializes
  236. * the group accordingly.
  237. *
  238. * @return array
  239. */
  240. public function groups() {
  241. if (empty($this->_compiledGroupNames)) {
  242. foreach ($this->settings['groups'] as $group) {
  243. $this->_compiledGroupNames[] = $this->settings['prefix'] . $group;
  244. }
  245. }
  246. $groups = $this->_Memcache->get($this->_compiledGroupNames);
  247. if (count($groups) !== count($this->settings['groups'])) {
  248. foreach ($this->_compiledGroupNames as $group) {
  249. if (!isset($groups[$group])) {
  250. $this->_Memcache->set($group, 1, false, 0);
  251. $groups[$group] = 1;
  252. }
  253. }
  254. ksort($groups);
  255. }
  256. $result = array();
  257. $groups = array_values($groups);
  258. foreach ($this->settings['groups'] as $i => $group) {
  259. $result[] = $group . $groups[$i];
  260. }
  261. return $result;
  262. }
  263. /**
  264. * Increments the group value to simulate deletion of all keys under a group
  265. * old values will remain in storage until they expire.
  266. *
  267. * @return boolean success
  268. */
  269. public function clearGroup($group) {
  270. return (bool)$this->_Memcache->increment($this->settings['prefix'] . $group);
  271. }
  272. }