Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Push — master ( e10d68...c33bed )
by Dan
04:37
created

MySqlDatabase::getDbBytes()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 4
rs 10
1
<?php declare(strict_types=1);
2
3
use Smr\Container\DiContainer;
4
use Smr\MySqlProperties;
5
6
class MySqlDatabase {
7
	private mysqli $dbConn;
8
	private MySqlProperties $mysqlProperties;
9
	private string $selectedDbName;
10
	/**
11
	 * @var bool | mysqli_result
12
	 */
13
	private $dbResult = null;
14
	private ?array $dbRecord = null;
15
16
	public static function getInstance(): MySqlDatabase {
17
		return DiContainer::make(self::class);
18
	}
19
20
	/**
21
	 * Reconnects to the MySQL database, and replaces the managed mysqli instance
22
	 * in the dependency injection container for future retrievals.
23
	 * @throws \DI\DependencyException
24
	 * @throws \DI\NotFoundException
25
	 */
26
	private static function reconnectMysql(): mysqli {
27
		$newMysqli = DiContainer::make(mysqli::class);
28
		DiContainer::getContainer()->set(mysqli::class, $newMysqli);
29
		return $newMysqli;
30
	}
31
32
	public static function mysqliFactory(MySqlProperties $mysqlProperties): mysqli {
33
		if (!mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT)) {
34
			throw new RuntimeException('Failed to enable mysqli error reporting');
35
		}
36
		$mysql = new mysqli(
37
			$mysqlProperties->getHost(),
38
			$mysqlProperties->getUser(),
39
			$mysqlProperties->getPassword(),
40
			$mysqlProperties->getDatabaseName());
41
		$charset = $mysql->character_set_name();
42
		if ($charset != 'utf8') {
43
			throw new RuntimeException('Unexpected charset: ' . $charset);
44
		}
45
		return $mysql;
46
	}
47
48
	/**
49
	 * MySqlDatabase constructor.
50
	 * Not intended to be constructed by hand. If you need an instance of MySqlDatabase,
51
	 * use MySqlDatabase::getInstance();
52
	 * @param ?mysqli $dbConn The mysqli instance (null if reconnect needed)
53
	 * @param MySqlProperties $mysqlProperties The properties object that was used to construct the mysqli instance
54
	 */
55
	public function __construct(?mysqli $dbConn, MySqlProperties $mysqlProperties) {
56
		if (is_null($dbConn)) {
57
			$dbConn = self::reconnectMysql();
58
		}
59
		$this->dbConn = $dbConn;
60
		$this->mysqlProperties = $mysqlProperties;
61
		$this->selectedDbName = $mysqlProperties->getDatabaseName();
62
	}
63
64
	/**
65
	 * This method will switch the connection to the specified database.
66
	 * Useful for switching back and forth between historical, and live databases.
67
	 *
68
	 * @param string $databaseName The name of the database to switch to
69
	 */
70
	public function switchDatabases(string $databaseName) {
71
		$this->dbConn->select_db($databaseName);
72
		$this->selectedDbName = $databaseName;
73
	}
74
75
	/**
76
	 * Switch back to the configured live database
77
	 */
78
	public function switchDatabaseToLive() {
79
		$this->switchDatabases($this->mysqlProperties->getDatabaseName());
80
	}
81
82
	/**
83
	 * Returns the size of the selected database in bytes.
84
	 */
85
	public function getDbBytes() {
86
		$query = 'SELECT SUM(data_length + index_length) as db_bytes FROM information_schema.tables WHERE table_schema=' . $this->escapeString($this->selectedDbName);
87
		$result = $this->dbConn->query($query);
88
		return (int)$result->fetch_assoc()['db_bytes'];
89
	}
90
91
	/**
92
	 * This should not be needed except perhaps by persistent connections
93
	 *
94
	 * Closes the connection to the MySQL database. After closing this connection,
95
	 * this MySqlDatabase instance is no longer valid, and will subsequently throw exceptions when
96
	 * attempting to perform database operations.
97
	 *
98
	 * You must call MySqlDatabase::getInstance() again to retrieve a valid instance that
99
	 * is reconnected to the database.
100
	 */
101
	public function close() {
102
		if ($this->dbConn) {
103
			$this->dbConn->close();
104
			unset($this->dbConn);
105
			// Set the mysqli instance in the dependency injection container to
106
			// null so that the MySqlDatabase constructor will reconnect the
107
			// next time it is called.
108
			DiContainer::getContainer()->set(mysqli::class, null);
109
		}
110
	}
111
112
	public function query($query) {
113
		$this->dbResult = $this->dbConn->query($query);
114
	}
115
116
	/**
117
	 * Use to populate this instance with the next record of the active query.
118
	 */
119
	public function nextRecord(): bool {
120
		if (!$this->dbResult) {
121
			$this->error('No resource to get record from.');
122
		}
123
		if ($this->dbRecord = $this->dbResult->fetch_assoc()) {
124
			return true;
125
		}
126
		return false;
127
	}
128
129
	/**
130
	 * Use instead of nextRecord when exactly one record is expected from the
131
	 * active query.
132
	 */
133
	public function requireRecord(): void {
134
		if (!$this->nextRecord() || $this->getNumRows() != 1) {
135
			$this->error('One record required, but found ' . $this->getNumRows());
136
		}
137
	}
138
139
	public function hasField($name) {
140
		return isset($this->dbRecord[$name]);
141
	}
142
143
	public function getField($name) {
144
		return $this->dbRecord[$name];
145
	}
146
147
	public function getBoolean($name) {
148
		if ($this->dbRecord[$name] == 'TRUE') {
149
			return true;
150
		}
151
//		if($this->dbRecord[$name] == 'FALSE')
152
		return false;
153
//		$this->error('Field is not a boolean');
154
	}
155
156
	public function getInt($name) {
157
		return (int)$this->dbRecord[$name];
158
	}
159
160
	public function getFloat($name) {
161
		return (float)$this->dbRecord[$name];
162
	}
163
164
	// WARNING: In the past, Microtime was stored in the database incorrectly.
165
	// For backwards compatibility, set $pad_msec=true to try to guess at the
166
	// intended value. This is not safe if the Microtime length is wrong for an
167
	// unrelated reason!
168
	public function getMicrotime($name, $pad_msec = false) {
169
		$data = $this->dbRecord[$name];
170
		$sec = substr($data, 0, 10);
171
		$msec = substr($data, 10);
172
		if (strlen($msec) != 6) {
173
			if ($pad_msec) {
174
				$msec = str_pad($msec, 6, '0', STR_PAD_LEFT);
175
			} else {
176
				$this->error('Field is not an escaped microtime (' . $data . ')');
177
			}
178
		}
179
		return "$sec.$msec";
180
	}
181
182
	public function getObject($name, $compressed = false) {
183
		$object = $this->getField($name);
184
		if ($compressed === true) {
185
			$object = gzuncompress($object);
186
		}
187
		return unserialize($object);
188
	}
189
190
	public function getRow() {
191
		return $this->dbRecord;
192
	}
193
194
	public function lockTable($table) {
195
		$this->dbConn->query('LOCK TABLES ' . $table . ' WRITE');
196
	}
197
198
	public function unlock() {
199
		$this->dbConn->query('UNLOCK TABLES');
200
	}
201
202
	public function getNumRows() {
203
		return $this->dbResult->num_rows;
204
	}
205
206
	public function getChangedRows() {
207
		return $this->dbConn->affected_rows;
208
	}
209
210
	public function getInsertID() {
211
		return $this->dbConn->insert_id;
212
	}
213
214
	protected function error($err) {
215
		throw new RuntimeException($err);
216
	}
217
218
	public function escape($escape, $autoQuotes = true, $quotes = true) {
219
		if (is_bool($escape)) {
220
			if ($autoQuotes) {
221
				return $this->escapeBoolean($escape);
222
			} else {
223
				return $this->escapeBoolean($escape, $quotes);
224
			}
225
		}
226
		if (is_numeric($escape)) {
227
			return $this->escapeNumber($escape);
228
		}
229
		if (is_string($escape)) {
230
			if ($autoQuotes) {
231
				return $this->escapeString($escape);
232
			} else {
233
				return $this->escapeString($escape, $quotes);
234
			}
235
		}
236
		if (is_array($escape)) {
237
			return $this->escapeArray($escape, $autoQuotes, $quotes);
238
		}
239
		if (is_object($escape)) {
240
			if ($autoQuotes) {
241
				return $this->escapeObject($escape);
242
			} else {
243
				return $this->escapeObject($escape, $quotes);
244
			}
245
		}
246
	}
247
248
	public function escapeString($string, $quotes = true, $nullable = false) {
249
		if ($nullable === true && ($string === null || $string === '')) {
250
			return 'NULL';
251
		}
252
		if ($string === true) {
253
			$string = 'TRUE';
254
		} elseif ($string === false) {
255
			$string = 'FALSE';
256
		}
257
		if (is_array($string)) {
258
			$escapedString = '';
259
			foreach ($string as $value) {
260
				$escapedString .= $this->escapeString($value, $quotes) . ',';
261
			}
262
			return substr($escapedString, 0, -1);
263
		}
264
		if ($quotes) {
265
			return '\'' . $this->dbConn->real_escape_string($string) . '\'';
266
		}
267
		return $this->dbConn->real_escape_string($string);
268
	}
269
270
	public function escapeBinary($binary) {
271
		return '0x' . bin2hex($binary);
272
	}
273
274
	public function escapeArray(array $array, $autoQuotes = true, $quotes = true, $implodeString = ',', $escapeIndividually = true) {
275
		$string = '';
276
		if ($escapeIndividually) {
277
			foreach ($array as $value) {
278
				if (is_array($value)) {
279
					$string .= $this->escapeArray($value, $autoQuotes, $quotes, $implodeString, $escapeIndividually) . $implodeString;
280
				} else {
281
					$string .= $this->escape($value, $autoQuotes, $quotes) . $implodeString;
282
				}
283
			}
284
			$string = substr($string, 0, -1);
285
		} else {
286
			$string = $this->escape(implode($implodeString, $array), $autoQuotes, $quotes);
287
		}
288
		return $string;
289
	}
290
291
	public function escapeNumber($num) {
292
		// Numbers need not be quoted in MySQL queries, so if we know $num is
293
		// numeric, we can simply return its value (no quoting or escaping).
294
		if (is_numeric($num)) {
295
			return $num;
296
		} else {
297
			$this->error('Not a number! (' . $num . ')');
298
		}
299
	}
300
301
	public function escapeMicrotime($microtime, $quotes = false) {
302
		$sec_str = sprintf('%010d', $microtime);
303
		$usec_str = sprintf('%06d', fmod($microtime, 1) * 1E6);
304
		return $this->escapeString($sec_str . $usec_str, $quotes);
305
	}
306
307
	public function escapeBoolean($bool, $quotes = true) {
308
		if ($bool === true) {
309
			return $this->escapeString('TRUE', $quotes);
310
		} elseif ($bool === false) {
311
			return $this->escapeString('FALSE', $quotes);
312
		} else {
313
			$this->error('Not a boolean: ' . $bool);
314
		}
315
	}
316
317
	public function escapeObject($object, $compress = false, $quotes = true, $nullable = false) {
318
		if ($compress === true) {
319
			return $this->escapeBinary(gzcompress(serialize($object)));
320
		}
321
		return $this->escapeString(serialize($object), $quotes, $nullable);
322
	}
323
}
324