Completed
Push — master ( 2617a4...acc0e0 )
by Nazar
05:19 queued 44s
created

DB::__call()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 9
rs 9.2
cc 4
eloc 7
nc 3
nop 2
1
<?php
2
/**
3
 * @package   CleverStyle CMS
4
 * @author    Nazar Mokrynskyi <[email protected]>
5
 * @copyright Copyright (c) 2011-2015, Nazar Mokrynskyi
6
 * @license   MIT License, see license.txt
7
 */
8
namespace cs;
9
10
/**
11
 * @method static DB 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
1 ignored issue
show
Documentation introduced by
Should the return type not be integer|double?

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...
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
	function db ($database_id) {
93
		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
	function db_prime ($database_id) {
117
		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 call_user_func_array([$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
	protected function generic_connecting ($database_id, $read_query) {
147
		if (!is_int($database_id) && $database_id != '0') {
148
			return False_class::instance();
149
		}
150
		/**
151
		 * Establish wne connection to the database
152
		 */
153
		$connection = $this->connecting($database_id, $read_query);
154
		/**
155
		 * If connection fails - try once more
156
		 */
157
		if (!$connection) {
158
			$connection = $this->connecting($database_id, $read_query);
159
		}
160
		/**
161
		 * If failed twice - show error
162
		 */
163
		if (!$connection) {
164
			throw new ExitException(500);
165
		}
166
		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
	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
		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
			$read_query &&
188
			isset($this->mirrors[$database_id])
189
		) {
190
			return $this->mirrors[$database_id];
191
		}
192
		/**
193
		 * If connection already established
194
		 */
195
		if (isset($this->connections[$database_id])) {
196
			return $this->connections[$database_id];
197
		}
198
		$Core = Core::instance();
199
		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
		$engine_class    = "cs\\DB\\$database_settings[type]";
206
		$connection      = new $engine_class(
207
			$database_settings['name'],
208
			$database_settings['user'],
209
			$database_settings['password'],
210
			$database_settings['host'],
211
			$database_settings['charset'],
212
			$database_settings['prefix']
213
		);
214
		$connection_name = ($database_id == 0 ? "Core DB ($Core->db_type)" : $database_id)."/$database_settings[host]/$database_settings[type]";
215
		unset($engine_class, $database_settings);
216
		/**
217
		 * If successfully - add connection to the list of success connections and return instance of DB engine object
218
		 */
219
		if (is_object($connection) && $connection->connected()) {
220
			$this->successful_connections[] = $connection_name;
221
			$this->$database_id             = $connection;
222
			if ($is_mirror) {
223
				$this->mirrors[$database_id] = $connection;
224
			} else {
225
				$this->connections[$database_id] = $connection;
226
			}
227
			return $connection;
228
		}
229
		/**
230
		 * If failed - add connection to the list of failed connections and log error
231
		 */
232
		$this->failed_connections[$database_id] = $connection_name;
233
		trigger_error(
234
			$database_id == 0 ? 'Error connecting to core DB of site' : "Error connecting to DB $connection_name",
235
			E_USER_ERROR
236
		);
237
		return False_class::instance();
238
	}
239
	/**
240
	 * Get database connection settings, depending on query type and system configuration settings of main db or one of mirrors might be returned
241
	 *
242
	 * @param int  $database_id
243
	 * @param bool $read_query
244
	 *
245
	 * @return array
246
	 */
247
	protected function get_db_connection_settings ($database_id, $read_query) {
248
		$Config = Config::instance();
249
		$Core   = Core::instance();
250
		/**
251
		 * Choose right mirror depending on system configuration
252
		 */
253
		$is_mirror    = false;
254
		$mirror_index = $this->choose_mirror($database_id, $read_query);
255
		if ($mirror_index === self::MASTER_MIRROR) {
256
			if ($database_id == 0) {
257
				$database_settings = [
258
					'type'     => $Core->db_type,
259
					'name'     => $Core->db_name,
260
					'user'     => $Core->db_user,
261
					'password' => $Core->db_password,
262
					'host'     => $Core->db_host,
263
					'charset'  => $Core->db_charset,
264
					'prefix'   => $Core->db_prefix
265
				];
266
			} else {
267
				$database_settings = $Config->db[$database_id];
268
			}
269
		} else {
270
			$database_settings = $Config->db[$database_id]['mirrors'][$mirror_index];
271
			$is_mirror         = true;
272
		}
273
		return [
274
			$database_settings,
275
			$is_mirror
276
		];
277
	}
278
	/**
279
	 * Choose index of DB mirrors among available
280
	 *
281
	 * @param int  $database_id
282
	 * @param bool $read_query
283
	 *
284
	 * @return int
285
	 */
286
	protected function choose_mirror ($database_id, $read_query = true) {
287
		$Config = Config::instance(true);
288
		/**
289
		 * $Config might be not initialized, so, check also for `$Config->core`
290
		 */
291
		if (
292
			!@$Config->core['db_balance'] ||
293
			!isset($Config->db[$database_id]['mirrors'][0])
294
		) {
295
			return self::MASTER_MIRROR;
296
		}
297
		$mirrors_count = count($Config->db[$database_id]['mirrors']);
298
		/**
299
		 * Main db should be excluded from read requests if writes to mirrors are not allowed
300
		 */
301
		$selected_mirror = mt_rand(
302
			0,
303
			$read_query && $Config->core['db_mirror_mode'] == self::MIRROR_MODE_MASTER_SLAVE ? $mirrors_count - 1 : $mirrors_count
304
		);
305
		/**
306
		 * Main DB assumed to be in the end of interval, that is why `$select_mirror < $mirrors_count` will correspond to one of available mirrors,
307
		 * and `$select_mirror == $mirrors_count` to master DB itself
308
		 */
309
		return $selected_mirror < $mirrors_count ? $selected_mirror : self::MASTER_MIRROR;
310
	}
311
}
312