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