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

Resque_Redis   A

Complexity

Total Complexity 42

Size/Duplication

Total Lines 276
Duplicated Lines 0 %

Test Coverage

Coverage 82.86%

Importance

Changes 14
Bugs 0 Features 2
Metric Value
eloc 129
c 14
b 0
f 2
dl 0
loc 276
ccs 58
cts 70
cp 0.8286
rs 9.0399
wmc 42

6 Methods

Rating   Name   Duplication   Size   Complexity  
A prefix() 0 6 3
C __construct() 0 40 11
A __call() 0 17 5
A getPrefix() 0 3 1
A removePrefix() 0 8 2
F parseDsn() 0 71 20

How to fix   Complexity   

Complex Class

Complex classes like Resque_Redis often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Resque_Redis, and based on these observations, apply Extract Interface, too.

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