Completed
Push — master ( 32eaad...6cdc53 )
by Nazar
04:10
created

DB::get_db_connection_settings()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 30
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 22
nc 3
nop 2
dl 0
loc 30
ccs 18
cts 18
cp 1
crap 3
rs 8.8571
c 0
b 0
f 0
1
<?php
2
/**
3
 * @package   CleverStyle Framework
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2016, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs;
9
10
/**
11
 * @method static $this instance($check = false)
12
 */
13
class DB {
14
	use
15
		Singleton;
16
	const CONNECTIONS_ALL           = null;
17
	const CONNECTIONS_FAILED        = 0;
18
	const CONNECTIONS_SUCCESSFUL    = 1;
19
	const CONNECTIONS_MIRRORS       = 'mirror';
20
	const MASTER_MIRROR             = -1;
21
	const MIRROR_MODE_MASTER_MASTER = 0;
22
	const MIRROR_MODE_MASTER_SLAVE  = 1;
23
	/**
24
	 * @var DB\_Abstract[]
25
	 */
26
	protected $connections = [];
27
	/**
28
	 * @var array
29
	 */
30
	protected $successful_connections = [];
31
	/**
32
	 * @var array
33
	 */
34
	protected $failed_connections = [];
35
	/**
36
	 * @var array
37
	 */
38
	protected $mirrors = [];
39
	/**
40
	 * Get list of connections of specified type
41
	 *
42
	 * @param bool|null|string $type One of constants `self::CONNECTIONS_*`
43
	 *
44
	 * @return array For `self::CONNECTIONS_ALL` array of successful connections with corresponding objects as values of array<br>
45
	 *               Otherwise array where keys are database ids and values are strings with information about database
46
	 */
47
	function get_connections_list ($type = self::CONNECTIONS_ALL) {
48
		if ($type == self::CONNECTIONS_FAILED) {
49
			return $this->failed_connections;
50
		}
51
		if ($type == self::CONNECTIONS_SUCCESSFUL) {
52
			return $this->successful_connections;
53
		}
54
		if ($type == self::CONNECTIONS_MIRRORS) {
55
			return $this->mirrors;
56
		}
57
		return $this->connections;
58
	}
59
	/**
60
	 * Total number of executed queries
61
	 *
62
	 * @return int
63
	 */
64
	function queries () {
65
		$queries = 0;
66
		foreach ($this->connections as $c) {
67
			$queries += $c->queries()['num'];
68
		}
69
		return $queries;
70
	}
71
	/**
72
	 * Total time spent on all queries and connections
73
	 *
74
	 * @return float
75
	 */
76
	function time () {
77
		$time = 0;
78
		foreach ($this->connections as $c) {
79
			$time += $c->connecting_time() + $c->time();
80
		}
81
		return $time;
82
	}
83
	/**
84
	 * Get database instance for read queries
85
	 *
86
	 * @param int $database_id
87
	 *
88
	 * @return DB\_Abstract|False_class Returns instance of False_class on failure
89
	 *
90
	 * @throws ExitException
91
	 */
92 6
	function db ($database_id) {
93 6
		return $this->generic_connecting($database_id, true);
94
	}
95
	/**
96
	 * Get database instance for read queries
97
	 *
98
	 * @param int $database_id
99
	 *
100
	 * @return DB\_Abstract|False_class Returns instance of False_class on failure
101
	 *
102
	 * @throws ExitException
103
	 */
104
	function __get ($database_id) {
105
		return $this->db($database_id);
106
	}
107
	/**
108
	 * Get database instance for write queries
109
	 *
110
	 * @param int $database_id
111
	 *
112
	 * @return DB\_Abstract|False_class Returns instance of False_class on failure
113
	 *
114
	 * @throws ExitException
115
	 */
116 26
	function db_prime ($database_id) {
117 26
		return $this->generic_connecting($database_id, false);
118
	}
119
	/**
120
	 * Get database instance for read queries or process implicit calls to methods of main database instance
121
	 *
122
	 * @param int|string $method
123
	 * @param array      $arguments
124
	 *
125
	 * @return DB\_Abstract|False_class Returns instance of False_class on failure
126
	 *
127
	 * @throws ExitException
128
	 */
129
	function __call ($method, $arguments) {
130
		if (is_int($method) || $method == '0') {
131
			return $this->db_prime($method);
132
		} elseif (method_exists('cs\\DB\\_Abstract', $method)) {
133
			return $this->db(0)->$method(...$arguments);
134
		} else {
135
			return False_class::instance();
136
		}
137
	}
138
	/**
139
	 * @param int  $database_id
140
	 * @param bool $read_query
141
	 *
142
	 * @return DB\_Abstract|False_class
143
	 *
144
	 * @throws ExitException
145
	 */
146 30
	protected function generic_connecting ($database_id, $read_query) {
147 30
		if (!is_int($database_id) && $database_id != '0') {
148
			return False_class::instance();
149
		}
150
		/**
151
		 * Establish wne connection to the database
152
		 */
153 30
		$connection = $this->connecting($database_id, $read_query);
154
		/**
155
		 * If connection fails - try once more
156
		 */
157 30
		if (!$connection) {
158
			$connection = $this->connecting($database_id, $read_query);
159
		}
160
		/**
161
		 * If failed twice - show error
162
		 */
163 30
		if (!$connection) {
164
			throw new ExitException(500);
165
		}
166 30
		return $connection;
167
	}
168
	/**
169
	 * Processing of all DB request
170
	 *
171
	 * @param int  $database_id
172
	 * @param bool $read_query
173
	 *
174
	 * @return DB\_Abstract|False_class
175
	 */
176 30
	protected function connecting ($database_id, $read_query = true) {
177
		/**
178
		 * If connection found in list of failed connections - return instance of False_class
179
		 */
180 30
		if (isset($this->failed_connections[$database_id])) {
181
			return False_class::instance();
182
		}
183
		/**
184
		 * If connection to DB mirror already established
185
		 */
186
		if (
187 30
			$read_query &&
188 30
			isset($this->mirrors[$database_id])
189
		) {
190
			return $this->mirrors[$database_id];
191
		}
192
		/**
193
		 * If connection already established
194
		 */
195 30
		if (isset($this->connections[$database_id])) {
196 22
			return $this->connections[$database_id];
197
		}
198 30
		$Core = Core::instance();
199 30
		list($database_settings, $is_mirror) = $this->get_db_connection_settings($database_id, $read_query);
200
		/**
201
		 * Establish new connection
202
		 *
203
		 * @var DB\_Abstract $connection
204
		 */
205 30
		$engine_class    = "cs\\DB\\$database_settings[type]";
206 30
		$connection      = new $engine_class(
207 30
			$database_settings['name'],
208 30
			$database_settings['user'],
209 30
			$database_settings['password'],
210 30
			$database_settings['host'],
211 30
			$database_settings['prefix']
212
		);
213 30
		$connection_name = ($database_id == 0 ? "Core DB ($Core->db_type)" : $database_id)."/$database_settings[host]/$database_settings[type]";
214 30
		unset($engine_class, $database_settings);
215
		/**
216
		 * If successfully - add connection to the list of success connections and return instance of DB engine object
217
		 */
218 30
		if (is_object($connection) && $connection->connected()) {
219 30
			$this->successful_connections[] = $connection_name;
220 30
			$this->$database_id             = $connection;
221 30
			if ($is_mirror) {
222 2
				$this->mirrors[$database_id] = $connection;
223
			} else {
224 28
				$this->connections[$database_id] = $connection;
225
			}
226 30
			return $connection;
227
		}
228
		/**
229
		 * If failed - add connection to the list of failed connections and log error
230
		 */
231
		$this->failed_connections[$database_id] = $connection_name;
232
		trigger_error(
233
			$database_id == 0 ? 'Error connecting to core DB of site' : "Error connecting to DB $connection_name",
234
			E_USER_ERROR
235
		);
236
		return False_class::instance();
237
	}
238
	/**
239
	 * Get database connection settings, depending on query type and system configuration settings of main db or one of mirrors might be returned
240
	 *
241
	 * @param int  $database_id
242
	 * @param bool $read_query
243
	 *
244
	 * @return array
245
	 */
246 30
	protected function get_db_connection_settings ($database_id, $read_query) {
247 30
		$Config = Config::instance();
248 30
		$Core   = Core::instance();
249
		/**
250
		 * Choose right mirror depending on system configuration
251
		 */
252 30
		$is_mirror    = false;
253 30
		$mirror_index = $this->choose_mirror($database_id, $read_query);
254 30
		if ($mirror_index === self::MASTER_MIRROR) {
255 28
			if ($database_id == 0) {
256
				$database_settings = [
257 28
					'type'     => $Core->db_type,
258 28
					'name'     => $Core->db_name,
259 28
					'user'     => $Core->db_user,
260 28
					'password' => $Core->db_password,
261 28
					'host'     => $Core->db_host,
262 28
					'prefix'   => $Core->db_prefix
263
				];
264
			} else {
265 28
				$database_settings = $Config->db[$database_id];
266
			}
267
		} else {
268 2
			$database_settings = $Config->db[$database_id]['mirrors'][$mirror_index];
269 2
			$is_mirror         = true;
270
		}
271
		return [
272 30
			$database_settings,
273 30
			$is_mirror
274
		];
275
	}
276
	/**
277
	 * Choose index of DB mirrors among available
278
	 *
279
	 * @param int  $database_id
280
	 * @param bool $read_query
281
	 *
282
	 * @return int
283
	 */
284 30
	protected function choose_mirror ($database_id, $read_query = true) {
285 30
		$Config = Config::instance(true);
286
		/**
287
		 * $Config might be not initialized, so, check also for `$Config->core`
288
		 */
289
		if (
290 30
			!@$Config->core['db_balance'] ||
291 30
			!isset($Config->db[$database_id]['mirrors'][0])
292
		) {
293 28
			return self::MASTER_MIRROR;
294
		}
295 2
		$mirrors_count = count($Config->db[$database_id]['mirrors']);
296
		/**
297
		 * Main db should be excluded from read requests if writes to mirrors are not allowed
298
		 */
299 2
		$selected_mirror = mt_rand(
300 2
			0,
301 2
			$read_query && $Config->core['db_mirror_mode'] == self::MIRROR_MODE_MASTER_SLAVE ? $mirrors_count - 1 : $mirrors_count
302
		);
303
		/**
304
		 * Main DB assumed to be in the end of interval, that is why `$select_mirror < $mirrors_count` will correspond to one of available mirrors,
305
		 * and `$select_mirror == $mirrors_count` to master DB itself
306
		 */
307 2
		return $selected_mirror < $mirrors_count ? $selected_mirror : self::MASTER_MIRROR;
308
	}
309
}
310