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 (#951)
by
unknown
04:36
created

MySqlDatabase::escapeNumber()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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