Test Failed
Push — master ( b6a920...fe41c0 )
by Hennik
01:09 queued 14s
created

Resque_Redis::parseDsn()   F

Complexity

Conditions 20
Paths 388

Size

Total Lines 71
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 20.0181

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 71
ccs 27
cts 28
cp 0.9643
crap 20.0181
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
 * 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
	/**
96
	 * Set Redis namespace (prefix) default: resque
97
	 * @param string $namespace
98
	 */
99 2
	public static function prefix($namespace)
100
	{
101 2
	    if (substr($namespace, -1) !== ':' && $namespace != '') {
102 2
	        $namespace .= ':';
103
	    }
104 2
	    self::$defaultNamespace = $namespace;
105 2
	}
106
107
	/**
108
	 * @param string|array $server A DSN or array
109
	 * @param int $database A database number to select. However, if we find a valid database number in the DSN the
110
	 *                      DSN-supplied value will be used instead and this parameter is ignored.
111
	 * @param object $client Optional Credis_Cluster or Credis_Client instance instantiated by you
112
	 */
113 71
    public function __construct($server, $database = null, $client = null)
114
	{
115
		try {
116 71
			if (is_object($client)) {
117 2
				$this->driver = $client;
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...
118
			}
119 70
			elseif (is_object($server)) {
0 ignored issues
show
introduced by
The condition is_object($server) is always false.
Loading history...
120
				$this->driver = $server;
121
			}
122 70
			elseif (is_array($server)) {
123
				$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

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