Completed
Push — master ( f9aba3...23d82e )
by Nazar
05:05
created

DB   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 241
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
dl 0
loc 241
ccs 82
cts 82
cp 1
rs 10
c 0
b 0
f 0
wmc 30
lcom 1
cbo 5

8 Methods

Rating   Name   Duplication   Size   Complexity  
A get_connections_list() 0 3 1
A queries() 0 13 3
A time() 0 13 3
A db() 0 3 1
A db_prime() 0 3 1
C connect() 0 71 12
B get_db_connection_settings() 0 30 3
B choose_mirror() 0 25 6
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_MASTER        = 0;
17
	const CONNECTIONS_MIRROR        = 1;
18
	const CONNECTIONS_SUCCESSFUL    = 2;
19
	const CONNECTIONS_FAILED        = 3;
20
	const MASTER_MIRROR             = -1;
21
	const MIRROR_MODE_MASTER_MASTER = 0;
22
	const MIRROR_MODE_MASTER_SLAVE  = 1;
23
	/**
24
	 * @var array[]
25
	 */
26
	protected $connections = [
27
		self::CONNECTIONS_MASTER     => [],
28
		self::CONNECTIONS_MIRROR     => [],
29
		self::CONNECTIONS_SUCCESSFUL => [],
30
		self::CONNECTIONS_FAILED     => [],
31
	];
32
	/**
33
	 * Get list of connections of specified type
34
	 *
35
	 * @param int $type One of constants `self::CONNECTIONS_*`
36
	 *
37
	 * @return array For `self::CONNECTIONS_MASTER` and `self::CONNECTIONS_MIRRORS` array of successful connections with corresponding objects as values of
38
	 *               array, otherwise array where keys are database ids and values are strings with information about database
39
	 */
40 2
	public function get_connections_list ($type) {
41 2
		return $this->connections[$type];
42
	}
43
	/**
44
	 * Total number of executed queries
45
	 *
46
	 * @return int
47
	 */
48 4
	public function queries () {
49 4
		$queries = 0;
50
		/**
51
		 * @var DB\_Abstract $c
52
		 */
53 4
		foreach ($this->connections[self::CONNECTIONS_MASTER] as $c) {
54 3
			$queries += $c->queries_count();
55
		}
56 4
		foreach ($this->connections[self::CONNECTIONS_MIRROR] as $c) {
57 1
			$queries += $c->queries_count();
58
		}
59 4
		return $queries;
60
	}
61
	/**
62
	 * Total time spent on all queries and connections
63
	 *
64
	 * @return float
65
	 */
66 4
	public function time () {
67 4
		$time = 0;
68
		/**
69
		 * @var DB\_Abstract $c
70
		 */
71 4
		foreach ($this->connections[self::CONNECTIONS_MASTER] as $c) {
72 3
			$time += $c->connecting_time() + $c->time();
73
		}
74 4
		foreach ($this->connections[self::CONNECTIONS_MIRROR] as $c) {
75 1
			$time += $c->connecting_time() + $c->time();
76
		}
77 4
		return $time;
78
	}
79
	/**
80
	 * Get database instance for read queries
81
	 *
82
	 * @param int $database_id
83
	 *
84
	 * @return DB\_Abstract|False_class Returns instance of False_class on failure
85
	 *
86
	 * @throws ExitException
87
	 */
88 38
	public function db ($database_id) {
89 38
		return $this->connect($database_id, true);
90
	}
91
	/**
92
	 * Get database instance for write queries
93
	 *
94
	 * @param int $database_id
95
	 *
96
	 * @return DB\_Abstract|False_class Returns instance of False_class on failure
97
	 *
98
	 * @throws ExitException
99
	 */
100 44
	public function db_prime ($database_id) {
101 44
		return $this->connect($database_id, false);
102
	}
103
	/**
104
	 * @param int  $database_id
105
	 * @param bool $read_query
106
	 *
107
	 * @return DB\_Abstract|False_class
108
	 *
109
	 * @throws ExitException
110
	 */
111 48
	protected function connect ($database_id, $read_query) {
112 48
		$database_id = (int)$database_id;
113
		/**
114
		 * If connection found in list of failed connections - return instance of False_class
115
		 */
116 48
		if (isset($this->connections[self::CONNECTIONS_FAILED][$database_id])) {
117 2
			return False_class::instance();
118
		}
119
		/**
120
		 * If connection to DB mirror already established
121
		 */
122
		if (
123 48
			$read_query &&
124 48
			isset($this->connections[self::CONNECTIONS_MIRROR][$database_id])
125
		) {
126 2
			return $this->connections[self::CONNECTIONS_MIRROR][$database_id];
127
		}
128
		/**
129
		 * If connection already established
130
		 */
131 48
		if (isset($this->connections[self::CONNECTIONS_MASTER][$database_id])) {
132 42
			return $this->connections[self::CONNECTIONS_MASTER][$database_id];
133
		}
134 48
		$Core = Core::instance();
135
		/**
136
		 * Try to connect second time if first time fails, probably, to another mirror
137
		 */
138 48
		for ($trial = 0; $trial < 2; ++$trial) {
139 48
			list($database_settings, $is_mirror) = $this->get_db_connection_settings($database_id, $read_query);
140
			/**
141
			 * Establish new connection
142
			 *
143
			 * @var DB\_Abstract $connection
144
			 */
145 48
			$engine_class    = "cs\\DB\\$database_settings[type]";
146 48
			$connection      = new $engine_class(
147 48
				$database_settings['name'],
148 48
				$database_settings['user'],
149 48
				$database_settings['password'],
150 48
				$database_settings['host'],
151 48
				$database_settings['prefix']
152
			);
153 48
			$connection_name = ($database_id == 0 ? "Core DB ($Core->db_type)" : $database_id)."/$database_settings[host]/$database_settings[type]";
154 48
			unset($engine_class, $database_settings);
155
			/**
156
			 * If successfully - add connection to the list of success connections and return instance of DB engine object
157
			 */
158 48
			if (is_object($connection) && $connection->connected()) {
159 48
				$this->connections[self::CONNECTIONS_SUCCESSFUL][] = $connection_name;
160 48
				if ($is_mirror) {
161 2
					$this->connections[self::CONNECTIONS_MIRROR][$database_id] = $connection;
162
				} else {
163 48
					$this->connections[self::CONNECTIONS_MASTER][$database_id] = $connection;
164
				}
165 48
				return $connection;
166
			}
167
		}
168
		/**
169
		 * If failed - add connection to the list of failed connections and log error
170
		 */
171
		/** @noinspection PhpUndefinedVariableInspection */
172 2
		$this->connections[self::CONNECTIONS_FAILED][$database_id] = $connection_name;
173 2
		trigger_error(
174 2
			$database_id == 0 ? 'Error connecting to core DB of site' : "Error connecting to DB $connection_name",
175 2
			E_USER_WARNING
176
		);
177 2
		if ($database_id == 0) {
178 2
			throw new ExitException('Error connecting to core DB of site', 500);
179
		}
180 2
		return False_class::instance();
181
	}
182
	/**
183
	 * Get database connection settings, depending on query type and system configuration settings of main db or one of mirrors might be returned
184
	 *
185
	 * @param int  $database_id
186
	 * @param bool $read_query
187
	 *
188
	 * @return array
189
	 */
190 48
	protected function get_db_connection_settings ($database_id, $read_query) {
191 48
		$Config = Config::instance();
192 48
		$Core   = Core::instance();
193
		/**
194
		 * Choose right mirror depending on system configuration
195
		 */
196 48
		$is_mirror    = false;
197 48
		$mirror_index = $this->choose_mirror($database_id, $read_query);
198 48
		if ($mirror_index === self::MASTER_MIRROR) {
199 48
			if ($database_id == 0) {
200
				$database_settings = [
201 48
					'type'     => $Core->db_type,
202 48
					'name'     => $Core->db_name,
203 48
					'user'     => $Core->db_user,
204 48
					'password' => $Core->db_password,
205 48
					'host'     => $Core->db_host,
206 48
					'prefix'   => $Core->db_prefix
207
				];
208
			} else {
209 48
				$database_settings = $Config->db[$database_id];
210
			}
211
		} else {
212 2
			$database_settings = $Config->db[$database_id]['mirrors'][$mirror_index];
213 2
			$is_mirror         = true;
214
		}
215
		return [
216 48
			$database_settings,
217 48
			$is_mirror
218
		];
219
	}
220
	/**
221
	 * Choose index of DB mirrors among available
222
	 *
223
	 * @param int  $database_id
224
	 * @param bool $read_query
225
	 *
226
	 * @return int
227
	 */
228 48
	protected function choose_mirror ($database_id, $read_query = true) {
229 48
		$Config = Config::instance(true);
230
		/**
231
		 * $Config might not be initialized yet
232
		 */
233
		if (
234 48
			!@$Config->core['db_balance'] ||
235 48
			!isset($Config->db[$database_id]['mirrors'][0])
236
		) {
237 48
			return self::MASTER_MIRROR;
238
		}
239 2
		$mirrors_count = count($Config->db[$database_id]['mirrors']);
240
		/**
241
		 * Main db should be excluded from read requests if writes to mirrors are not allowed
242
		 */
243 2
		$selected_mirror = mt_rand(
244 2
			0,
245 2
			$read_query && $Config->core['db_mirror_mode'] == self::MIRROR_MODE_MASTER_SLAVE ? $mirrors_count - 1 : $mirrors_count
246
		);
247
		/**
248
		 * Main DB assumed to be in the end of interval, that is why `$select_mirror < $mirrors_count` will correspond to one of available mirrors,
249
		 * and `$select_mirror == $mirrors_count` to master DB itself
250
		 */
251 2
		return $selected_mirror < $mirrors_count ? $selected_mirror : self::MASTER_MIRROR;
252
	}
253
}
254