Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
12694 anikendra 1
<?php defined('BASEPATH') OR exit('No direct script access allowed');
2
/**
3
 * CodeIgniter Redis
4
 *
5
 * A CodeIgniter library to interact with Redis
6
 *
7
 * @package          CodeIgniter
8
 * @category    	Libraries
9
 * @author        	Jo‘l Cox
10
 * @version			v0.4
11
 * @link 			https://github.com/joelcox/codeigniter-redis
12
 * @link			http://joelcox.nl
13
 * @license         http://www.opensource.org/licenses/mit-license.html
14
 */
15
class CI_Redis {
16
 
17
	/**
18
	 * CI
19
	 *
20
	 * CodeIgniter instance
21
	 * @var 	object
22
	 */
23
	private $_ci;
24
 
25
	/**
26
	 * Connection
27
	 *
28
	 * Socket handle to the Redis server
29
	 * @var		handle
30
	 */
31
	private $_connection;
32
 
33
	/**
34
	 * Debug
35
	 *
36
	 * Whether we're in debug mode
37
	 * @var		bool
38
	 */
39
	public $debug = FALSE;
40
 
41
	/**
42
	 * CRLF
43
	 *
44
	 * User to delimiter arguments in the Redis unified request protocol
45
	 * @var		string
46
	 */
47
	const CRLF = "\r\n";
48
 
49
	/**
50
	 * Constructor
51
	 */
52
	public function __construct($params = array())
53
	{
54
 
55
		log_message('debug', 'Redis Class Initialized');
56
 
57
		$this->_ci = get_instance();
58
		$this->_ci->load->config('redis');
59
 
60
		// Check for the different styles of configs
61
		if (isset($params['connection_group']))
62
		{
63
			// Specific connection group
64
			$config = $this->_ci->config->item('redis_' . $params['connection_group']);
65
		}
66
		elseif (is_array($this->_ci->config->item('redis_default')))
67
		{
68
			// Default connection group
69
			$config = $this->_ci->config->item('redis_default');
70
		}
71
		else
72
		{
73
			// Original config style
74
			$config = array(
75
				'host' => $this->_ci->config->item('redis_host'),
76
				'port' => $this->_ci->config->item('redis_port'),
77
				'password' => $this->_ci->config->item('redis_password'),
78
			);
79
		}
80
 
81
		// Connect to Redis
82
		$this->_connection = @fsockopen($config['host'], $config['port'], $errno, $errstr, 3);
83
 
84
		// Display an error message if connection failed
85
		if ( ! $this->_connection)
86
		{
87
			show_error('Could not connect to Redis at ' . $config['host'] . ':' . $config['port']);
88
		}
89
 
90
		// Authenticate when needed
91
		$this->_auth($config['password']);
92
 
93
	}
94
 
95
	/**
96
	 * Call
97
	 *
98
	 * Catches all undefined methods
99
	 * @param	string	method that was called
100
	 * @param	mixed	arguments that were passed
101
	 * @return 	mixed
102
	 */
103
	public function __call($method, $arguments)
104
	{
105
		$request = $this->_encode_request($method, $arguments);
106
		return $this->_write_request($request);
107
	}
108
 
109
	/**
110
	 * Command
111
	 *
112
	 * Generic command function, just like redis-cli
113
	 * @param	string	full command as a string
114
	 * @return 	mixed
115
	 */
116
	public function command($string)
117
	{
118
		$slices = explode(' ', $string);
119
		$request = $this->_encode_request($slices[0], array_slice($slices, 1));
120
 
121
		return $this->_write_request($request);
122
	}
123
 
124
	/**
125
	 * Auth
126
	 *
127
	 * Runs the AUTH command when password is set
128
	 * @param 	string	password for the Redis server
129
	 * @return 	void
130
	 */
131
	private function _auth($password = NULL)
132
	{
133
 
134
		// Authenticate when password is set
135
		if ( ! empty($password))
136
		{
137
 
138
			// See if we authenticated successfully
139
			if ($this->command('AUTH ' . $password) !== 'OK')
140
			{
141
				show_error('Could not connect to Redis, invalid password');
142
			}
143
 
144
		}
145
 
146
	}
147
 
148
	/**
149
	 * Write request
150
	 *
151
	 * Write the formatted request to the socket
152
	 * @param	string 	request to be written
153
	 * @return 	mixed
154
	 */
155
	private function _write_request($request)
156
	{
157
 
158
		if ($this->debug === TRUE)
159
		{
160
			log_message('debug', 'Redis unified request: ' . $request);
161
		}
162
 
163
		fwrite($this->_connection, $request);
164
		return $this->_read_request();
165
 
166
	}
167
 
168
	/**
169
	 * Read request
170
	 *
171
	 * Route each response to the appropriate interpreter
172
	 * @return 	mixed
173
	 */
174
	private function _read_request()
175
	{
176
 
177
		$type = fgetc($this->_connection);
178
 
179
		if ($this->debug === TRUE)
180
		{
181
			log_message('debug', 'Redis response type: ' . $type);
182
		}
183
 
184
		switch ($type)
185
		{
186
			case '+':
187
				return $this->_single_line_reply();
188
				break;
189
			case '-':
190
				return $this->_error_reply();
191
				break;
192
			case ':':
193
				return $this->_integer_reply();
194
				break;
195
			case '$':
196
				return $this->_bulk_reply();
197
				break;
198
			case '*':
199
				return $this->_multi_bulk_reply();
200
				break;
201
			default:
202
				return FALSE;
203
		}
204
 
205
	}
206
 
207
	/**
208
	 * Single line reply
209
	 *
210
	 * Reads the reply before the EOF
211
	 * @return 	mixed
212
	 */
213
	private function _single_line_reply()
214
	{
215
		$value = trim(fgets($this->_connection));
216
		return $value;
217
	}
218
 
219
	/**
220
	 * Error reply
221
	 *
222
	 * Write error to log and return false
223
	 * @return 	bool
224
	 */
225
	private function _error_reply()
226
	{
227
		// Extract the error message
228
		$error = substr(fgets($this->_connection), 4);
229
		log_message('error', 'Redis server returned an error: ' . $error);
230
 
231
		return FALSE;
232
	}
233
 
234
	/**
235
	 * Integer reply
236
	 *
237
	 * Returns an integer reply
238
	 * @return 	int
239
	 */
240
	private function _integer_reply()
241
	{
242
		return (int) fgets($this->_connection);
243
	}
244
 
245
	/**
246
	 * Bulk reply
247
	 *
248
	 * Reads to amount of bits to be read and returns value within
249
	 * the pointer and the ending delimiter
250
	 * @return 	string
251
	 */
252
	private function _bulk_reply()
253
	{
254
		// Get the amount of bits to be read
255
		$value_length = (int) fgets($this->_connection);
256
 
257
		if ($value_length <= 0) return NULL;
258
 
259
		$read = 0;
260
		$response = '';
261
 
262
		// handle if reply data more than 8192 bytes.
263
		while ($read < $value_length)
264
		{
265
		  $remaining = $value_length - $read;
266
 
267
		  $block = $remaining < 8192 ? $remaining : 8192;
268
 
269
		  $response .= rtrim(fread($this->_connection, $block));
270
 
271
		  $read += $block;
272
		}
273
 
274
		// Make sure to remove the new line and carriage from the socket buffer
275
		fgets($this->_connection);
276
		return isset($response) ? $response : FALSE;
277
	}
278
 
279
	/**
280
	 * Multi bulk reply
281
	 *
282
	 * Reads n bulk replies and return them as an array
283
	 * @return 	array
284
	 */
285
	private function _multi_bulk_reply()
286
	{
287
		// Get the amount of values in the response
288
		$total_values = (int) fgets($this->_connection);
289
 
290
		// Loop all values and add them to the response array
291
		for ($i = 0; $i < $total_values; $i++)
292
		{
293
			// Remove the new line and carriage return before reading
294
			// another bulk reply
295
			fgets($this->_connection, 2);
296
			$response[] = $this->_bulk_reply();
297
		}
298
 
299
		return isset($response) ? $response : FALSE;
300
	}
301
 
302
	/**
303
	 * Encode request
304
	 *
305
	 * Encode plain-text request to Redis protocol format
306
	 * @link 	http://redis.io/topics/protocol
307
	 * @param 	string 	request in plain-text
308
	 * @param   string  additional data (string or array, depending on the request)
309
	 * @return 	string 	encoded according to Redis protocol
310
	 */
311
	private function _encode_request($method, $arguments = array())
312
	{
313
		$argument_count = $this->_count_arguments($arguments);
314
 
315
		// Set the argument count and prepend the method
316
		$request = '*' . $argument_count . self::CRLF;
317
		$request .= '$' . strlen($method) . self::CRLF . $method . self::CRLF;
318
 
319
		if ($argument_count === 1) return $request;
320
 
321
		// Append all the arguments in the request string
322
		foreach ($arguments as $argument)
323
		{
324
 
325
			if (is_array($argument))
326
			{
327
				$is_associative_array = self::is_associative_array($argument);
328
 
329
				foreach ($argument as $key => $value)
330
				{
331
					// Prepend the key if we're dealing with a hash
332
					if ($is_associative_array)
333
					{
334
						$request .= '$' . strlen($key) . self::CRLF . $key . self::CRLF;
335
					}
336
 
337
					$request .= '$' . strlen($value) . self::CRLF . $value . self::CRLF;
338
				}
339
			}
340
			else
341
			{
342
				$request .= '$' . strlen($argument) . self::CRLF . $argument . self::CRLF;
343
			}
344
 
345
		}
346
 
347
		return $request;
348
	}
349
 
350
	/**
351
	 * Count arguments
352
	 *
353
	 * Count the amount of arguments we need to pass to Redis while taking
354
	 * into consideration lists, hashes and strings
355
	 */
356
	private function _count_arguments($arguments)
357
	{
358
		$argument_count = 1;
359
 
360
		// Count how many arguments we need to push over the wire
361
		foreach ($arguments as $argument)
362
		{
363
 
364
			// We're dealing with 2n arguments if we're consider the
365
			// keys as arguments too.
366
			if (is_array($argument) AND self::is_associative_array($argument))
367
			{
368
				$argument_count += (count($argument) * 2);
369
			}
370
			elseif (is_array($argument))
371
			{
372
				$argument_count += count($argument);
373
			}
374
			else
375
			{
376
				$argument_count++;
377
			}
378
 
379
		}
380
 
381
		return $argument_count;
382
 
383
	}
384
 
385
	/**
386
	 * Info
387
	 *
388
	 * Overrides the default Redis response, so we can return a nice array
389
	 * of the server info instead of a nasty string.
390
	 * @return 	array
391
	 */
392
	public function info()
393
	{
394
		$response = $this->command('INFO');
395
		$data = array();
396
		$lines = explode(self::CRLF, $response);
397
 
398
		// Extract the key and value
399
		foreach ($lines as $line)
400
		{
401
			$parts = explode(':', $line);
402
			if (isset($parts[1])) $data[$parts[0]] = $parts[1];
403
		}
404
 
405
		return $data;
406
	}
407
 
408
	/**
409
	 * Debug
410
	 *
411
	 * Set debug mode
412
	 * @param	bool 	set the debug mode on or off
413
	 * @return 	void
414
	 */
415
	public function debug($bool)
416
	{
417
		$this->debug = (bool) $bool;
418
	}
419
 
420
	/**
421
	 * Destructor
422
	 *
423
	 * Kill the connection
424
	 * @return 	void
425
	 */
426
	function __destruct()
427
	{
428
	  if($this->_connection)
429
	  {
430
		  fclose($this->_connection);
431
		}
432
	}
433
 
434
	/**
435
	 * Is associative array
436
	 *
437
	 * Checkes whether the array has only intergers as key, starting at
438
	 * index 0, untill the array length - 1.
439
	 * @param 	array 	the array to be checked
440
	 * @return 	bool
441
	 */
442
	public static function is_associative_array($array)
443
	{
444
		$keys = array_keys($array);
445
 
446
		if (min($keys) === 0 AND max($keys) === count($array) - 1) return FALSE;
447
		return TRUE;
448
	}
449
 
450
}