Completed
Pull Request — master (#20)
by
unknown
03:15
created

Resque_Redis::getPrefix()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 1
c 1
b 0
f 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
/**
3
 * Wrap Credis to add namespace support and various helper methods.
4
 *
5
 * @package		Resque/Redis
6
 * @author		Chris Boulton <[email protected]>
7
 * @license		http://www.opensource.org/licenses/mit-license.php
8
 */
9
class Resque_Redis
10
{
11
	/**
12
	 * Redis namespace
13
	 * @var string
14
	 */
15
	private static $defaultNamespace = 'resque:';
16
17
	/**
18
	 * A default host to connect to
19
	 */
20
	const DEFAULT_HOST = 'localhost';
21
22
	/**
23
	 * The default Redis port
24
	 */
25
	const DEFAULT_PORT = 6379;
26
27
	/**
28
	 * The default Redis Database number
29
	 */
30
	const DEFAULT_DATABASE = 0;
31
32
	/**
33
	 * @var array List of all commands in Redis that supply a key as their
34
	 *	first argument. Used to prefix keys with the Resque namespace.
35
	 */
36
	private $keyCommands = array(
37
		'exists',
38
		'del',
39
		'type',
40
		'keys',
41
		'expire',
42
		'ttl',
43
		'move',
44
		'set',
45
		'setex',
46
		'get',
47
		'getset',
48
		'setnx',
49
		'incr',
50
		'incrby',
51
		'decr',
52
		'decrby',
53
		'rpush',
54
		'lpush',
55
		'llen',
56
		'lrange',
57
		'ltrim',
58
		'lindex',
59
		'lset',
60
		'lrem',
61
		'lpop',
62
		'blpop',
63
		'rpop',
64
		'sadd',
65
		'srem',
66
		'spop',
67
		'scard',
68
		'sismember',
69
		'smembers',
70
		'srandmember',
71
		'zadd',
72
		'zrem',
73
		'zrange',
74
		'zrevrange',
75
		'zrangebyscore',
76
		'zcard',
77
		'zscore',
78
		'zremrangebyscore',
79
		'sort',
80
		'rename',
81
		'rpoplpush'
82
	);
83
	// sinterstore
84
	// sunion
85
	// sunionstore
86
	// sdiff
87
	// sdiffstore
88
	// sinter
89
	// smove
90
	// mget
91
	// msetnx
92
	// mset
93
	// renamenx
94
95
	private $scanCommands = array(
96
		'scan',
97
		'hscan',
98
		'sscan',
99
		'zscan',
100
	);
101
102
	/**
103
	 * Set Redis namespace (prefix) default: resque
104
	 * @param string $namespace
105
	 */
106 2
	public static function prefix($namespace)
107
	{
108 2
	    if (substr($namespace, -1) !== ':' && $namespace != '') {
109 2
	        $namespace .= ':';
110
	    }
111 2
	    self::$defaultNamespace = $namespace;
112 2
	}
113
114
	/**
115
	 * @param string|array $server A DSN or array
116
	 * @param int $database A database number to select. However, if we find a valid database number in the DSN the
117
	 *                      DSN-supplied value will be used instead and this parameter is ignored.
118
	 * @param object $client Optional Credis_Cluster or Credis_Client instance instantiated by you
119
	 */
120 68
    public function __construct($server, $database = null, $client = null)
121
	{
122
		try {
123 68
			if (is_array($server)) {
124
				$this->driver = new Credis_Cluster($server);
0 ignored issues
show
Bug Best Practice introduced by
The property driver does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
Deprecated Code introduced by
The class Credis_Cluster has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

124
				$this->driver = /** @scrutinizer ignore-deprecated */ new Credis_Cluster($server);
Loading history...
125
			}
126 68
			else if (is_object($client)) {
127 2
				$this->driver = $client;
128
			}
129
			else {
130 67
				list($host, $port, $dsnDatabase, $user, $password, $options) = self::parseDsn($server);
131
				// $user is not used, only $password
132
133
				// Look for known Credis_Client options
134 67
				$timeout = isset($options['timeout']) ? intval($options['timeout']) : null;
135 67
				$persistent = isset($options['persistent']) ? $options['persistent'] : '';
136 67
				$maxRetries = isset($options['max_connect_retries']) ? $options['max_connect_retries'] : 0;
137
138 67
				$this->driver = new Credis_Client($host, $port, $timeout, $persistent);
139 67
				$this->driver->setMaxConnectRetries($maxRetries);
140 67
				if ($password){
141
					$this->driver->auth($password);
142
				}
143
144
				// If we have found a database in our DSN, use it instead of the `$database`
145
				// value passed into the constructor.
146 67
				if ($dsnDatabase !== false) {
147
					$database = $dsnDatabase;
148
				}
149
			}
150
151 68
			if ($database !== null) {
152 68
				$this->driver->select($database);
153
			}
154
		}
155 2
		catch(CredisException $e) {
156 2
			throw new Resque_RedisException('Error communicating with Redis: ' . $e->getMessage(), 0, $e);
157
		}
158 67
	}
159
160
	/**
161
	 * Parse a DSN string, which can have one of the following formats:
162
	 *
163
	 * - host:port
164
	 * - redis://user:pass@host:port/db?option1=val1&option2=val2
165
	 * - tcp://user:pass@host:port/db?option1=val1&option2=val2
166
	 * - unix:///path/to/redis.sock
167
	 *
168
	 * Note: the 'user' part of the DSN is not used.
169
	 *
170
	 * @param string $dsn A DSN string
171
	 * @return array An array of DSN compotnents, with 'false' values for any unknown components. e.g.
172
	 *               [host, port, db, user, pass, options]
173
	 */
174 88
	public static function parseDsn($dsn)
175
	{
176 88
		if ($dsn == '') {
177
			// Use a sensible default for an empty DNS string
178 1
			$dsn = 'redis://' . self::DEFAULT_HOST;
179
		}
180 88
		if(substr($dsn, 0, 7) === 'unix://') {
181
			return array(
182
				$dsn,
183
				null,
184
				false,
185
				null,
186
				null,
187
				null,
188
			);
189
		}
190 88
		$parts = parse_url($dsn);
191
192
		// Check the URI scheme
193 88
		$validSchemes = array('redis', 'tcp');
194 88
		if (isset($parts['scheme']) && ! in_array($parts['scheme'], $validSchemes)) {
195 3
			throw new \InvalidArgumentException("Invalid DSN. Supported schemes are " . implode(', ', $validSchemes));
196
		}
197
198
		// Allow simple 'hostname' format, which `parse_url` treats as a path, not host.
199 85
		if ( ! isset($parts['host']) && isset($parts['path'])) {
200 1
			$parts['host'] = $parts['path'];
201 1
			unset($parts['path']);
202
		}
203
204
		// Extract the port number as an integer
205 85
		$port = isset($parts['port']) ? intval($parts['port']) : self::DEFAULT_PORT;
206
207
		// Get the database from the 'path' part of the URI
208 85
		$database = false;
209 85
		if (isset($parts['path'])) {
210
			// Strip non-digit chars from path
211 7
			$database = intval(preg_replace('/[^0-9]/', '', $parts['path']));
212
		}
213
214
		// Extract any 'user' and 'pass' values
215 85
		$user = isset($parts['user']) ? $parts['user'] : false;
216 85
		$pass = isset($parts['pass']) ? $parts['pass'] : false;
217
218
		// Convert the query string into an associative array
219 85
		$options = array();
220 85
		if (isset($parts['query'])) {
221
			// Parse the query string into an array
222 6
			parse_str($parts['query'], $options);
223
		}
224
225
		return array(
226 85
			$parts['host'],
227 85
			$port,
228 85
			$database,
229 85
			$user,
230 85
			$pass,
231 85
			$options,
232
		);
233
	}
234
235
	/**
236
	 * Magic method to handle all function requests and prefix key based
237
	 * operations with the {self::$defaultNamespace} key prefix.
238
	 *
239
	 * @param string $name The name of the method called.
240
	 * @param array $args Array of supplied arguments to the method.
241
	 * @return mixed Return value from Resident::call() based on the command.
242
	 */
243 73
	public function __call($name, $args)
244
	{
245 73
		if (in_array($name, $this->keyCommands)) {
246 73
			if (is_array($args[0])) {
247 1
				foreach ($args[0] AS $i => $v) {
248 1
					$args[0][$i] = self::$defaultNamespace . $v;
249
				}
250
			}
251
			else {
252 73
				$args[0] = self::$defaultNamespace . $args[0];
253
			}
254
		} elseif (in_array($name, $this->scanCommands)) {
255
			// fix scan cursor to be a reference.
256
			$args[0] = &$args[0];
257
		}
258
		try {
259 73
			return $this->driver->__call($name, $args);
260
		}
261
		catch (CredisException $e) {
262
			throw new Resque_RedisException('Error communicating with Redis: ' . $e->getMessage(), 0, $e);
263
		}
264
	}
265
266 13
	public static function getPrefix()
267
	{
268 13
	    return self::$defaultNamespace;
269
	}
270
271
	public static function removePrefix($string)
272
	{
273
	    $prefix=self::getPrefix();
274
275
	    if (substr($string, 0, strlen($prefix)) == $prefix) {
276
	        $string = substr($string, strlen($prefix), strlen($string) );
277
	    }
278
	    return $string;
279
	}
280
}
281