Resque_Redis::parseDsn()   F
last analyzed

Complexity

Conditions 20
Paths 388

Size

Total Lines 69
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 20.0202

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 20
eloc 38
c 4
b 0
f 0
nc 388
nop 1
dl 0
loc 69
ccs 26
cts 27
cp 0.963
crap 20.0202
rs 0.9833

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * Wrap Credis to add namespace support and various helper methods.
5
 *
6
 * @package		Resque/Redis
7
 * @author		Chris Boulton <[email protected]>
8
 * @license		http://www.opensource.org/licenses/mit-license.php
9
 */
10
class Resque_Redis
11
{
12
	/**
13
	 * Redis namespace
14
	 * @var string
15
	 */
16
	private static $defaultNamespace = 'resque:';
17
18
	/**
19
	 * A default host to connect to
20
	 */
21
	const DEFAULT_HOST = 'localhost';
22
23
	/**
24
	 * The default Redis port
25
	 */
26
	const DEFAULT_PORT = 6379;
27
28
	/**
29
	 * The default Redis Database number
30
	 */
31
	const DEFAULT_DATABASE = 0;
32
33
	/**
34
	 * Connection driver
35
	 * @var mixed
36
	 */
37
	private $driver;
38
39
	/**
40
	 * @var array List of all commands in Redis that supply a key as their
41
	 *	first argument. Used to prefix keys with the Resque namespace.
42
	 */
43
	private $keyCommands = array(
44
		'exists',
45
		'del',
46
		'type',
47
		'keys',
48
		'expire',
49
		'ttl',
50
		'move',
51
		'set',
52
		'setex',
53
		'get',
54
		'getset',
55
		'setnx',
56
		'incr',
57
		'incrby',
58
		'decr',
59
		'decrby',
60
		'rpush',
61
		'lpush',
62
		'llen',
63
		'lrange',
64
		'ltrim',
65
		'lindex',
66
		'lset',
67
		'lrem',
68
		'lpop',
69
		'blpop',
70
		'rpop',
71
		'sadd',
72
		'srem',
73
		'spop',
74
		'scard',
75
		'sismember',
76
		'smembers',
77
		'srandmember',
78
		'zadd',
79
		'zrem',
80
		'zrange',
81
		'zrevrange',
82
		'zrangebyscore',
83
		'zcard',
84
		'zscore',
85
		'zremrangebyscore',
86
		'sort',
87
		'rename',
88
		'rpoplpush'
89
	);
90
	// sinterstore
91
	// sunion
92
	// sunionstore
93
	// sdiff
94
	// sdiffstore
95
	// sinter
96
	// smove
97
	// mget
98
	// msetnx
99 2
	// mset
100
	// renamenx
101 2
102 2
	/**
103
	 * Set Redis namespace (prefix) default: resque
104 2
	 * @param string $namespace
105 2
	 */
106
	public static function prefix($namespace)
107
	{
108
		if (substr($namespace, -1) !== ':' && $namespace != '') {
109
			$namespace .= ':';
110
		}
111
		self::$defaultNamespace = $namespace;
112
	}
113 71
114
	/**
115
	 * @param string|array $server A DSN or array
116 71
	 * @param int $database A database number to select. However, if we find a valid database number in the DSN the
117 2
	 *                      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 70
	 */
120
	public function __construct($server, $database = null, $client = null)
121
	{
122 70
		try {
123
			if (is_object($client)) {
124
				$this->driver = $client;
125
			} elseif (is_object($server)) {
0 ignored issues
show
introduced by
The condition is_object($server) is always false.
Loading history...
126 70
				$this->driver = $server;
127
			} elseif (is_array($server)) {
128
				$this->driver = new Credis_Cluster($server);
0 ignored issues
show
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

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