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
Pull Request — master (#924)
by Dan
04:13
created

MySqlDatabase::mysqliFactory()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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