RedisFactory::create()   F
last analyzed

Complexity

Conditions 20
Paths 1274

Size

Total Lines 98
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 20
eloc 54
nc 1274
nop 0
dl 0
loc 98
rs 0
c 1
b 0
f 0

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
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Alejandro Varela <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Robin McCorkell <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program. If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
namespace OC;
28
29
use OCP\Diagnostics\IEventLogger;
30
31
class RedisFactory {
32
	public const REDIS_MINIMAL_VERSION = '3.1.3';
33
	public const REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION = '5.3.0';
34
35
	/** @var  \Redis|\RedisCluster */
36
	private $instance;
37
38
	private SystemConfig $config;
39
40
	private IEventLogger $eventLogger;
41
42
	/**
43
	 * RedisFactory constructor.
44
	 *
45
	 * @param SystemConfig $config
46
	 */
47
	public function __construct(SystemConfig $config, IEventLogger $eventLogger) {
48
		$this->config = $config;
49
		$this->eventLogger = $eventLogger;
50
	}
51
52
	private function create() {
53
		$isCluster = in_array('redis.cluster', $this->config->getKeys(), true);
54
		$config = $isCluster
55
			? $this->config->getValue('redis.cluster', [])
56
			: $this->config->getValue('redis', []);
57
58
		if ($isCluster && !class_exists('RedisCluster')) {
59
			throw new \Exception('Redis Cluster support is not available');
60
		}
61
62
		if (isset($config['timeout'])) {
63
			$timeout = $config['timeout'];
64
		} else {
65
			$timeout = 0.0;
66
		}
67
68
		if (isset($config['read_timeout'])) {
69
			$readTimeout = $config['read_timeout'];
70
		} else {
71
			$readTimeout = 0.0;
72
		}
73
74
		$auth = null;
75
		if (isset($config['password']) && (string)$config['password'] !== '') {
76
			if (isset($config['user']) && (string)$config['user'] !== '') {
77
				$auth = [$config['user'], $config['password']];
78
			} else {
79
				$auth = $config['password'];
80
			}
81
		}
82
83
		// # TLS support
84
		// # https://github.com/phpredis/phpredis/issues/1600
85
		$connectionParameters = $this->getSslContext($config);
86
87
		// cluster config
88
		if ($isCluster) {
89
			if (!isset($config['seeds'])) {
90
				throw new \Exception('Redis cluster config is missing the "seeds" attribute');
91
			}
92
93
			// Support for older phpredis versions not supporting connectionParameters
94
			if ($connectionParameters !== null) {
95
				$this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, true, $auth, $connectionParameters);
0 ignored issues
show
Unused Code introduced by
The call to RedisCluster::__construct() has too many arguments starting with $connectionParameters. ( Ignorable by Annotation )

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

95
				$this->instance = /** @scrutinizer ignore-call */ new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, true, $auth, $connectionParameters);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
96
			} else {
97
				$this->instance = new \RedisCluster(null, $config['seeds'], $timeout, $readTimeout, true, $auth);
98
			}
99
100
			if (isset($config['failover_mode'])) {
101
				$this->instance->setOption(\RedisCluster::OPT_SLAVE_FAILOVER, $config['failover_mode']);
102
			}
103
		} else {
104
			$this->instance = new \Redis();
105
106
			if (isset($config['host'])) {
107
				$host = $config['host'];
108
			} else {
109
				$host = '127.0.0.1';
110
			}
111
112
			if (isset($config['port'])) {
113
				$port = $config['port'];
114
			} elseif ($host[0] !== '/') {
115
				$port = 6379;
116
			} else {
117
				$port = null;
118
			}
119
120
			$this->eventLogger->start('connect:redis', 'Connect to redis and send AUTH, SELECT');
121
			// Support for older phpredis versions not supporting connectionParameters
122
			if ($connectionParameters !== null) {
123
				// Non-clustered redis requires connection parameters to be wrapped inside `stream`
124
				$connectionParameters = [
125
					'stream' => $this->getSslContext($config)
126
				];
127
				/**
128
				 * even though the stubs and documentation don't want you to know this,
129
				 * pconnect does have the same $connectionParameters argument connect has
130
				 *
131
				 * https://github.com/phpredis/phpredis/blob/0264de1824b03fb2d0ad515b4d4ec019cd2dae70/redis.c#L710-L730
132
				 *
133
				 * @psalm-suppress TooManyArguments
134
				 */
135
				$this->instance->pconnect($host, $port, $timeout, null, 0, $readTimeout, $connectionParameters);
0 ignored issues
show
Unused Code introduced by
The call to Redis::pconnect() has too many arguments starting with $connectionParameters. ( Ignorable by Annotation )

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

135
				$this->instance->/** @scrutinizer ignore-call */ 
136
                     pconnect($host, $port, $timeout, null, 0, $readTimeout, $connectionParameters);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
136
			} else {
137
				$this->instance->pconnect($host, $port, $timeout, null, 0, $readTimeout);
138
			}
139
140
141
			// Auth if configured
142
			if ($auth !== null) {
143
				$this->instance->auth($auth);
144
			}
145
146
			if (isset($config['dbindex'])) {
147
				$this->instance->select($config['dbindex']);
148
			}
149
			$this->eventLogger->end('connect:redis');
150
		}
151
	}
152
153
	/**
154
	 * Get the ssl context config
155
	 *
156
	 * @param array $config the current config
157
	 * @return array|null
158
	 * @throws \UnexpectedValueException
159
	 */
160
	private function getSslContext($config) {
161
		if (isset($config['ssl_context'])) {
162
			if (!$this->isConnectionParametersSupported()) {
163
				throw new \UnexpectedValueException(\sprintf(
164
					'php-redis extension must be version %s or higher to support ssl context',
165
					self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION
166
				));
167
			}
168
			return $config['ssl_context'];
169
		}
170
		return null;
171
	}
172
173
	public function getInstance() {
174
		if (!$this->isAvailable()) {
175
			throw new \Exception('Redis support is not available');
176
		}
177
		if (!$this->instance instanceof \Redis) {
178
			$this->create();
179
		}
180
181
		return $this->instance;
182
	}
183
184
	public function isAvailable(): bool {
185
		return \extension_loaded('redis') &&
186
			\version_compare(\phpversion('redis'), self::REDIS_MINIMAL_VERSION, '>=');
187
	}
188
189
	/**
190
	 * Php redis does support configurable extra parameters since version 5.3.0, see: https://github.com/phpredis/phpredis#connect-open.
191
	 * We need to check if the current version supports extra connection parameters, otherwise the connect method will throw an exception
192
	 *
193
	 * @return boolean
194
	 */
195
	private function isConnectionParametersSupported(): bool {
196
		return \extension_loaded('redis') &&
197
			\version_compare(\phpversion('redis'), self::REDIS_EXTRA_PARAMETERS_MINIMAL_VERSION, '>=');
198
	}
199
}
200