Completed
Push — master ( 8915f6...c874b7 )
by Nazar
06:07
created

DB   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 3
Bugs 2 Features 0
Metric Value
c 3
b 2
f 0
dl 0
loc 235
ccs 82
cts 82
cp 1
rs 10
wmc 30
lcom 1
cbo 5

8 Methods

Rating   Name   Duplication   Size   Complexity  
A get_connections_list() 0 3 1
A queries() 0 10 3
A time() 0 10 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 4
		foreach ($this->connections[self::CONNECTIONS_MASTER] as $c) {
51 2
			$queries += $c->queries()['num'];
52
		}
53 4
		foreach ($this->connections[self::CONNECTIONS_MIRROR] as $c) {
54 2
			$queries += $c->queries()['num'];
55
		}
56 4
		return $queries;
57
	}
58
	/**
59
	 * Total time spent on all queries and connections
60
	 *
61
	 * @return float
1 ignored issue
show
Documentation introduced by
Should the return type not be integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
62
	 */
63 4
	public function time () {
64 4
		$time = 0;
65 4
		foreach ($this->connections[self::CONNECTIONS_MASTER] as $c) {
66 2
			$time += $c->connecting_time() + $c->time();
67
		}
68 4
		foreach ($this->connections[self::CONNECTIONS_MIRROR] as $c) {
69 2
			$time += $c->connecting_time() + $c->time();
70
		}
71 4
		return $time;
72
	}
73
	/**
74
	 * Get database instance for read queries
75
	 *
76
	 * @param int $database_id
77
	 *
78
	 * @return DB\_Abstract|False_class Returns instance of False_class on failure
79
	 *
80
	 * @throws ExitException
81
	 */
82 36
	public function db ($database_id) {
83 36
		return $this->connect($database_id, true);
84
	}
85
	/**
86
	 * Get database instance for write queries
87
	 *
88
	 * @param int $database_id
89
	 *
90
	 * @return DB\_Abstract|False_class Returns instance of False_class on failure
91
	 *
92
	 * @throws ExitException
93
	 */
94 40
	public function db_prime ($database_id) {
95 40
		return $this->connect($database_id, false);
96
	}
97
	/**
98
	 * @param int  $database_id
99
	 * @param bool $read_query
100
	 *
101
	 * @return DB\_Abstract|False_class
102
	 *
103
	 * @throws ExitException
104
	 */
105 44
	protected function connect ($database_id, $read_query) {
106 44
		$database_id = (int)$database_id;
107
		/**
108
		 * If connection found in list of failed connections - return instance of False_class
109
		 */
110 44
		if (isset($this->connections[self::CONNECTIONS_FAILED][$database_id])) {
111 2
			return False_class::instance();
112
		}
113
		/**
114
		 * If connection to DB mirror already established
115
		 */
116
		if (
117 44
			$read_query &&
118 44
			isset($this->connections[self::CONNECTIONS_MIRROR][$database_id])
119
		) {
120 2
			return $this->connections[self::CONNECTIONS_MIRROR][$database_id];
121
		}
122
		/**
123
		 * If connection already established
124
		 */
125 44
		if (isset($this->connections[self::CONNECTIONS_MASTER][$database_id])) {
126 40
			return $this->connections[self::CONNECTIONS_MASTER][$database_id];
127
		}
128 44
		$Core = Core::instance();
129
		/**
130
		 * Try to connect second time if first time fails, probably, to another mirror
131
		 */
132 44
		for ($trial = 0; $trial < 2; ++$trial) {
133 44
			list($database_settings, $is_mirror) = $this->get_db_connection_settings($database_id, $read_query);
134
			/**
135
			 * Establish new connection
136
			 *
137
			 * @var DB\_Abstract $connection
138
			 */
139 44
			$engine_class    = "cs\\DB\\$database_settings[type]";
140 44
			$connection      = new $engine_class(
141 44
				$database_settings['name'],
142 44
				$database_settings['user'],
143 44
				$database_settings['password'],
144 44
				$database_settings['host'],
145 44
				$database_settings['prefix']
146
			);
147 44
			$connection_name = ($database_id == 0 ? "Core DB ($Core->db_type)" : $database_id)."/$database_settings[host]/$database_settings[type]";
148 44
			unset($engine_class, $database_settings);
149
			/**
150
			 * If successfully - add connection to the list of success connections and return instance of DB engine object
151
			 */
152 44
			if (is_object($connection) && $connection->connected()) {
153 44
				$this->connections[self::CONNECTIONS_SUCCESSFUL][] = $connection_name;
154 44
				if ($is_mirror) {
155 2
					$this->connections[self::CONNECTIONS_MIRROR][$database_id] = $connection;
156
				} else {
157 44
					$this->connections[self::CONNECTIONS_MASTER][$database_id] = $connection;
158
				}
159 44
				return $connection;
160
			}
161
		}
162
		/**
163
		 * If failed - add connection to the list of failed connections and log error
164
		 */
165
		/** @noinspection PhpUndefinedVariableInspection */
166 2
		$this->connections[self::CONNECTIONS_FAILED][$database_id] = $connection_name;
167 2
		trigger_error(
168 2
			$database_id == 0 ? 'Error connecting to core DB of site' : "Error connecting to DB $connection_name",
169 2
			E_USER_WARNING
170
		);
171 2
		if ($database_id == 0) {
172 2
			throw new ExitException('Error connecting to core DB of site', 500);
173
		}
174 2
		return False_class::instance();
175
	}
176
	/**
177
	 * Get database connection settings, depending on query type and system configuration settings of main db or one of mirrors might be returned
178
	 *
179
	 * @param int  $database_id
180
	 * @param bool $read_query
181
	 *
182
	 * @return array
183
	 */
184 44
	protected function get_db_connection_settings ($database_id, $read_query) {
185 44
		$Config = Config::instance();
186 44
		$Core   = Core::instance();
187
		/**
188
		 * Choose right mirror depending on system configuration
189
		 */
190 44
		$is_mirror    = false;
191 44
		$mirror_index = $this->choose_mirror($database_id, $read_query);
192 44
		if ($mirror_index === self::MASTER_MIRROR) {
193 44
			if ($database_id == 0) {
194
				$database_settings = [
195 44
					'type'     => $Core->db_type,
196 44
					'name'     => $Core->db_name,
197 44
					'user'     => $Core->db_user,
198 44
					'password' => $Core->db_password,
199 44
					'host'     => $Core->db_host,
200 44
					'prefix'   => $Core->db_prefix
201
				];
202
			} else {
203 44
				$database_settings = $Config->db[$database_id];
204
			}
205
		} else {
206 2
			$database_settings = $Config->db[$database_id]['mirrors'][$mirror_index];
207 2
			$is_mirror         = true;
208
		}
209
		return [
210 44
			$database_settings,
211 44
			$is_mirror
212
		];
213
	}
214
	/**
215
	 * Choose index of DB mirrors among available
216
	 *
217
	 * @param int  $database_id
218
	 * @param bool $read_query
219
	 *
220
	 * @return int
221
	 */
222 44
	protected function choose_mirror ($database_id, $read_query = true) {
223 44
		$Config = Config::instance(true);
224
		/**
225
		 * $Config might not be initialized yet
226
		 */
227
		if (
228 44
			!@$Config->core['db_balance'] ||
229 44
			!isset($Config->db[$database_id]['mirrors'][0])
230
		) {
231 44
			return self::MASTER_MIRROR;
232
		}
233 2
		$mirrors_count = count($Config->db[$database_id]['mirrors']);
234
		/**
235
		 * Main db should be excluded from read requests if writes to mirrors are not allowed
236
		 */
237 2
		$selected_mirror = mt_rand(
238 2
			0,
239 2
			$read_query && $Config->core['db_mirror_mode'] == self::MIRROR_MODE_MASTER_SLAVE ? $mirrors_count - 1 : $mirrors_count
240
		);
241
		/**
242
		 * Main DB assumed to be in the end of interval, that is why `$select_mirror < $mirrors_count` will correspond to one of available mirrors,
243
		 * and `$select_mirror == $mirrors_count` to master DB itself
244
		 */
245 2
		return $selected_mirror < $mirrors_count ? $selected_mirror : self::MASTER_MIRROR;
246
	}
247
}
248