Completed
Push — master ( a8898a...5c0dbc )
by Nazar
04:15
created

DB   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 266
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Test Coverage

Coverage 67.78%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 266
ccs 61
cts 90
cp 0.6778
rs 9.2
wmc 34
lcom 1
cbo 6

9 Methods

Rating   Name   Duplication   Size   Complexity  
A db() 0 3 1
A get_connections_list() 0 12 4
A db_prime() 0 3 1
B generic_connecting() 0 22 5
B get_db_connection_settings() 0 30 3
B choose_mirror() 0 25 6
A queries() 0 7 2
A time() 0 7 2
C connecting() 0 62 10
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 4
	function db ($database_id) {
93 4
		return $this->generic_connecting($database_id, true);
94
	}
95
	/**
96
	 * Get database instance for write 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 2
	function db_prime ($database_id) {
105 2
		return $this->generic_connecting($database_id, false);
106
	}
107
	/**
108
	 * @param int  $database_id
109
	 * @param bool $read_query
110
	 *
111
	 * @return DB\_Abstract|False_class
112
	 *
113
	 * @throws ExitException
114
	 */
115 6
	protected function generic_connecting ($database_id, $read_query) {
116 6
		if (!is_int($database_id) && $database_id != '0') {
117
			return False_class::instance();
118
		}
119
		/**
120
		 * Establish wne connection to the database
121
		 */
122 6
		$connection = $this->connecting($database_id, $read_query);
123
		/**
124
		 * If connection fails - try once more
125
		 */
126 6
		if (!$connection) {
127
			$connection = $this->connecting($database_id, $read_query);
128
		}
129
		/**
130
		 * If failed twice - show error
131
		 */
132 6
		if (!$connection) {
133
			throw new ExitException(500);
134
		}
135 6
		return $connection;
136
	}
137
	/**
138
	 * Processing of all DB request
139
	 *
140
	 * @param int  $database_id
141
	 * @param bool $read_query
142
	 *
143
	 * @return DB\_Abstract|False_class
144
	 */
145 6
	protected function connecting ($database_id, $read_query = true) {
146
		/**
147
		 * If connection found in list of failed connections - return instance of False_class
148
		 */
149 6
		if (isset($this->failed_connections[$database_id])) {
150
			return False_class::instance();
151
		}
152
		/**
153
		 * If connection to DB mirror already established
154
		 */
155
		if (
156 6
			$read_query &&
157 6
			isset($this->mirrors[$database_id])
158
		) {
159
			return $this->mirrors[$database_id];
160
		}
161
		/**
162
		 * If connection already established
163
		 */
164 6
		if (isset($this->connections[$database_id])) {
165
			return $this->connections[$database_id];
166
		}
167 6
		$Core = Core::instance();
168 6
		list($database_settings, $is_mirror) = $this->get_db_connection_settings($database_id, $read_query);
169
		/**
170
		 * Establish new connection
171
		 *
172
		 * @var DB\_Abstract $connection
173
		 */
174 6
		$engine_class    = "cs\\DB\\$database_settings[type]";
175 6
		$connection      = new $engine_class(
176 6
			$database_settings['name'],
177 6
			$database_settings['user'],
178 6
			$database_settings['password'],
179 6
			$database_settings['host'],
180 6
			$database_settings['prefix']
181
		);
182 6
		$connection_name = ($database_id == 0 ? "Core DB ($Core->db_type)" : $database_id)."/$database_settings[host]/$database_settings[type]";
183 6
		unset($engine_class, $database_settings);
184
		/**
185
		 * If successfully - add connection to the list of success connections and return instance of DB engine object
186
		 */
187 6
		if (is_object($connection) && $connection->connected()) {
188 6
			$this->successful_connections[] = $connection_name;
189 6
			$this->$database_id             = $connection;
190 6
			if ($is_mirror) {
191 2
				$this->mirrors[$database_id] = $connection;
192
			} else {
193 4
				$this->connections[$database_id] = $connection;
194
			}
195 6
			return $connection;
196
		}
197
		/**
198
		 * If failed - add connection to the list of failed connections and log error
199
		 */
200
		$this->failed_connections[$database_id] = $connection_name;
201
		trigger_error(
202
			$database_id == 0 ? 'Error connecting to core DB of site' : "Error connecting to DB $connection_name",
203
			E_USER_ERROR
204
		);
205
		return False_class::instance();
206
	}
207
	/**
208
	 * Get database connection settings, depending on query type and system configuration settings of main db or one of mirrors might be returned
209
	 *
210
	 * @param int  $database_id
211
	 * @param bool $read_query
212
	 *
213
	 * @return array
214
	 */
215 6
	protected function get_db_connection_settings ($database_id, $read_query) {
216 6
		$Config = Config::instance();
217 6
		$Core   = Core::instance();
218
		/**
219
		 * Choose right mirror depending on system configuration
220
		 */
221 6
		$is_mirror    = false;
222 6
		$mirror_index = $this->choose_mirror($database_id, $read_query);
223 6
		if ($mirror_index === self::MASTER_MIRROR) {
224 4
			if ($database_id == 0) {
225
				$database_settings = [
226 4
					'type'     => $Core->db_type,
227 4
					'name'     => $Core->db_name,
228 4
					'user'     => $Core->db_user,
229 4
					'password' => $Core->db_password,
230 4
					'host'     => $Core->db_host,
231 4
					'prefix'   => $Core->db_prefix
232
				];
233
			} else {
234 4
				$database_settings = $Config->db[$database_id];
235
			}
236
		} else {
237 2
			$database_settings = $Config->db[$database_id]['mirrors'][$mirror_index];
238 2
			$is_mirror         = true;
239
		}
240
		return [
241 6
			$database_settings,
242 6
			$is_mirror
243
		];
244
	}
245
	/**
246
	 * Choose index of DB mirrors among available
247
	 *
248
	 * @param int  $database_id
249
	 * @param bool $read_query
250
	 *
251
	 * @return int
252
	 */
253 6
	protected function choose_mirror ($database_id, $read_query = true) {
254 6
		$Config = Config::instance(true);
255
		/**
256
		 * $Config might be not initialized, so, check also for `$Config->core`
257
		 */
258
		if (
259 6
			!@$Config->core['db_balance'] ||
260 6
			!isset($Config->db[$database_id]['mirrors'][0])
261
		) {
262 4
			return self::MASTER_MIRROR;
263
		}
264 2
		$mirrors_count = count($Config->db[$database_id]['mirrors']);
265
		/**
266
		 * Main db should be excluded from read requests if writes to mirrors are not allowed
267
		 */
268 2
		$selected_mirror = mt_rand(
269 2
			0,
270 2
			$read_query && $Config->core['db_mirror_mode'] == self::MIRROR_MODE_MASTER_SLAVE ? $mirrors_count - 1 : $mirrors_count
271
		);
272
		/**
273
		 * Main DB assumed to be in the end of interval, that is why `$select_mirror < $mirrors_count` will correspond to one of available mirrors,
274
		 * and `$select_mirror == $mirrors_count` to master DB itself
275
		 */
276 2
		return $selected_mirror < $mirrors_count ? $selected_mirror : self::MASTER_MIRROR;
277
	}
278
}
279