1: <?php
2: /**
3: * Redis storage engine for cache
4: *
5: * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
6: * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
7: *
8: * Licensed under The MIT License
9: * For full copyright and license information, please see the LICENSE.txt
10: * Redistributions of files must retain the above copyright notice.
11: *
12: * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
13: * @link http://cakephp.org CakePHP(tm) Project
14: * @package Cake.Cache.Engine
15: * @since CakePHP(tm) v 2.2
16: * @license http://www.opensource.org/licenses/mit-license.php MIT License
17: */
18:
19: /**
20: * Redis storage engine for cache.
21: *
22: * @package Cake.Cache.Engine
23: */
24: class RedisEngine extends CacheEngine {
25:
26: /**
27: * Redis wrapper.
28: *
29: * @var Redis
30: */
31: protected $_Redis = null;
32:
33: /**
34: * Settings
35: *
36: * - server = string URL or ip to the Redis server host
37: * - database = integer database number to use for connection
38: * - port = integer port number to the Redis server (default: 6379)
39: * - timeout = float timeout in seconds (default: 0)
40: * - persistent = boolean Connects to the Redis server with a persistent connection (default: true)
41: * - unix_socket = path to the unix socket file (default: false)
42: *
43: * @var array
44: */
45: public $settings = array();
46:
47: /**
48: * Initialize the Cache Engine
49: *
50: * Called automatically by the cache frontend
51: * To reinitialize the settings call Cache::engine('EngineName', [optional] settings = array());
52: *
53: * @param array $settings array of setting for the engine
54: * @return bool True if the engine has been successfully initialized, false if not
55: */
56: public function init($settings = array()) {
57: if (!class_exists('Redis')) {
58: return false;
59: }
60: parent::init(array_merge(array(
61: 'engine' => 'Redis',
62: 'prefix' => null,
63: 'server' => '127.0.0.1',
64: 'database' => 0,
65: 'port' => 6379,
66: 'password' => false,
67: 'timeout' => 0,
68: 'persistent' => true,
69: 'unix_socket' => false
70: ), $settings)
71: );
72:
73: return $this->_connect();
74: }
75:
76: /**
77: * Connects to a Redis server
78: *
79: * @return bool True if Redis server was connected
80: */
81: protected function _connect() {
82: $return = false;
83: try {
84: $this->_Redis = new Redis();
85: if (!empty($this->settings['unix_socket'])) {
86: $return = $this->_Redis->connect($this->settings['unix_socket']);
87: } elseif (empty($this->settings['persistent'])) {
88: $return = $this->_Redis->connect($this->settings['server'], $this->settings['port'], $this->settings['timeout']);
89: } else {
90: $persistentId = $this->settings['port'] . $this->settings['timeout'] . $this->settings['database'];
91: $return = $this->_Redis->pconnect($this->settings['server'], $this->settings['port'], $this->settings['timeout'], $persistentId);
92: }
93: } catch (RedisException $e) {
94: return false;
95: }
96: if ($return && $this->settings['password']) {
97: $return = $this->_Redis->auth($this->settings['password']);
98: }
99: if ($return) {
100: $return = $this->_Redis->select($this->settings['database']);
101: }
102: return $return;
103: }
104:
105: /**
106: * Write data for key into cache.
107: *
108: * @param string $key Identifier for the data
109: * @param mixed $value Data to be cached
110: * @param int $duration How long to cache the data, in seconds
111: * @return bool True if the data was successfully cached, false on failure
112: */
113: public function write($key, $value, $duration) {
114: if (!is_int($value)) {
115: $value = serialize($value);
116: }
117: if ($duration === 0) {
118: return $this->_Redis->set($key, $value);
119: }
120:
121: return $this->_Redis->setex($key, $duration, $value);
122: }
123:
124: /**
125: * Read a key from the cache
126: *
127: * @param string $key Identifier for the data
128: * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
129: */
130: public function read($key) {
131: $value = $this->_Redis->get($key);
132: if (ctype_digit($value)) {
133: $value = (int)$value;
134: }
135: if ($value !== false && is_string($value)) {
136: $value = unserialize($value);
137: }
138: return $value;
139: }
140:
141: /**
142: * Increments the value of an integer cached key
143: *
144: * @param string $key Identifier for the data
145: * @param int $offset How much to increment
146: * @return New incremented value, false otherwise
147: * @throws CacheException when you try to increment with compress = true
148: */
149: public function increment($key, $offset = 1) {
150: return (int)$this->_Redis->incrBy($key, $offset);
151: }
152:
153: /**
154: * Decrements the value of an integer cached key
155: *
156: * @param string $key Identifier for the data
157: * @param int $offset How much to subtract
158: * @return New decremented value, false otherwise
159: * @throws CacheException when you try to decrement with compress = true
160: */
161: public function decrement($key, $offset = 1) {
162: return (int)$this->_Redis->decrBy($key, $offset);
163: }
164:
165: /**
166: * Delete a key from the cache
167: *
168: * @param string $key Identifier for the data
169: * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
170: */
171: public function delete($key) {
172: return $this->_Redis->delete($key) > 0;
173: }
174:
175: /**
176: * Delete all keys from the cache
177: *
178: * @param bool $check Whether or not expiration keys should be checked. If
179: * true, no keys will be removed as cache will rely on redis TTL's.
180: * @return bool True if the cache was successfully cleared, false otherwise
181: */
182: public function clear($check) {
183: if ($check) {
184: return true;
185: }
186: $keys = $this->_Redis->getKeys($this->settings['prefix'] . '*');
187: $this->_Redis->del($keys);
188:
189: return true;
190: }
191:
192: /**
193: * Returns the `group value` for each of the configured groups
194: * If the group initial value was not found, then it initializes
195: * the group accordingly.
196: *
197: * @return array
198: */
199: public function groups() {
200: $result = array();
201: foreach ($this->settings['groups'] as $group) {
202: $value = $this->_Redis->get($this->settings['prefix'] . $group);
203: if (!$value) {
204: $value = 1;
205: $this->_Redis->set($this->settings['prefix'] . $group, $value);
206: }
207: $result[] = $group . $value;
208: }
209: return $result;
210: }
211:
212: /**
213: * Increments the group value to simulate deletion of all keys under a group
214: * old values will remain in storage until they expire.
215: *
216: * @param string $group The group name to clear.
217: * @return bool success
218: */
219: public function clearGroup($group) {
220: return (bool)$this->_Redis->incr($this->settings['prefix'] . $group);
221: }
222:
223: /**
224: * Disconnects from the redis server
225: */
226: public function __destruct() {
227: if (!$this->settings['persistent']) {
228: $this->_Redis->close();
229: }
230: }
231: }
232: