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' => Inflector::slug(APP_DIR) . '_',
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: try {
83: $this->_Redis = new Redis();
84: if (!empty($this->settings['unix_socket'])) {
85: $return = $this->_Redis->connect($this->settings['unix_socket']);
86: } elseif (empty($this->settings['persistent'])) {
87: $return = $this->_Redis->connect($this->settings['server'], $this->settings['port'], $this->settings['timeout']);
88: } else {
89: $persistentId = $this->settings['port'] . $this->settings['timeout'] . $this->settings['database'];
90: $return = $this->_Redis->pconnect($this->settings['server'], $this->settings['port'], $this->settings['timeout'], $persistentId);
91: }
92: } catch (RedisException $e) {
93: $return = false;
94: }
95: if (!$return) {
96: return false;
97: }
98: if ($this->settings['password'] && !$this->_Redis->auth($this->settings['password'])) {
99: return false;
100: }
101: return $this->_Redis->select($this->settings['database']);
102: }
103:
104: /**
105: * Write data for key into cache.
106: *
107: * @param string $key Identifier for the data
108: * @param mixed $value Data to be cached
109: * @param int $duration How long to cache the data, in seconds
110: * @return bool True if the data was successfully cached, false on failure
111: */
112: public function write($key, $value, $duration) {
113: if (!is_int($value)) {
114: $value = serialize($value);
115: }
116: if ($duration === 0) {
117: return $this->_Redis->set($key, $value);
118: }
119:
120: return $this->_Redis->setex($key, $duration, $value);
121: }
122:
123: /**
124: * Read a key from the cache
125: *
126: * @param string $key Identifier for the data
127: * @return mixed The cached data, or false if the data doesn't exist, has expired, or if there was an error fetching it
128: */
129: public function read($key) {
130: $value = $this->_Redis->get($key);
131: if (ctype_digit($value)) {
132: $value = (int)$value;
133: }
134: if ($value !== false && is_string($value)) {
135: $value = unserialize($value);
136: }
137: return $value;
138: }
139:
140: /**
141: * Increments the value of an integer cached key
142: *
143: * @param string $key Identifier for the data
144: * @param int $offset How much to increment
145: * @return New incremented value, false otherwise
146: * @throws CacheException when you try to increment with compress = true
147: */
148: public function increment($key, $offset = 1) {
149: return (int)$this->_Redis->incrBy($key, $offset);
150: }
151:
152: /**
153: * Decrements the value of an integer cached key
154: *
155: * @param string $key Identifier for the data
156: * @param int $offset How much to subtract
157: * @return New decremented value, false otherwise
158: * @throws CacheException when you try to decrement with compress = true
159: */
160: public function decrement($key, $offset = 1) {
161: return (int)$this->_Redis->decrBy($key, $offset);
162: }
163:
164: /**
165: * Delete a key from the cache
166: *
167: * @param string $key Identifier for the data
168: * @return bool True if the value was successfully deleted, false if it didn't exist or couldn't be removed
169: */
170: public function delete($key) {
171: return $this->_Redis->delete($key) > 0;
172: }
173:
174: /**
175: * Delete all keys from the cache
176: *
177: * @param bool $check Whether or not expiration keys should be checked. If
178: * true, no keys will be removed as cache will rely on redis TTL's.
179: * @return bool True if the cache was successfully cleared, false otherwise
180: */
181: public function clear($check) {
182: if ($check) {
183: return true;
184: }
185: $keys = $this->_Redis->getKeys($this->settings['prefix'] . '*');
186: $this->_Redis->del($keys);
187:
188: return true;
189: }
190:
191: /**
192: * Returns the `group value` for each of the configured groups
193: * If the group initial value was not found, then it initializes
194: * the group accordingly.
195: *
196: * @return array
197: */
198: public function groups() {
199: $result = array();
200: foreach ($this->settings['groups'] as $group) {
201: $value = $this->_Redis->get($this->settings['prefix'] . $group);
202: if (!$value) {
203: $value = 1;
204: $this->_Redis->set($this->settings['prefix'] . $group, $value);
205: }
206: $result[] = $group . $value;
207: }
208: return $result;
209: }
210:
211: /**
212: * Increments the group value to simulate deletion of all keys under a group
213: * old values will remain in storage until they expire.
214: *
215: * @param string $group The group name to clear.
216: * @return bool success
217: */
218: public function clearGroup($group) {
219: return (bool)$this->_Redis->incr($this->settings['prefix'] . $group);
220: }
221:
222: /**
223: * Disconnects from the redis server
224: */
225: public function __destruct() {
226: if (!$this->settings['persistent']) {
227: $this->_Redis->close();
228: }
229: }
230: }
231: