Db   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 371
Duplicated Lines 0 %

Test Coverage

Coverage 64.71%

Importance

Changes 0
Metric Value
wmc 32
eloc 144
dl 0
loc 371
ccs 55
cts 85
cp 0.6471
rs 9.84
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A isTableExists() 0 3 1
A getPrimaryKey() 0 12 4
B getDbInfo() 0 41 6
A getConfig() 0 6 2
A createPdoInstance() 0 16 3
A setConfig() 0 3 1
A getUniqueID() 0 16 2
A getInstance() 0 9 2
A getTableKeys() 0 18 5
A getLastInsertID() 0 3 1
A createTable() 0 7 2
A quoteSql() 0 3 1
A getInfo() 0 29 2
1
<?php
2
3
namespace App;
4
5
/**
6
 * Database connection class.
7
 *
8
 * @package App
9
 *
10
 * @copyright YetiForce S.A.
11
 * @license   YetiForce Public License 6.5 (licenses/LicenseEN.txt or yetiforce.com)
12
 * @author    Mariusz Krzaczkowski <[email protected]>
13
 * @author    Radosław Skrzypczak <[email protected]>
14
 */
15
class Db extends \yii\db\Connection
16
{
17
	/**
18
	 * Sorting order flag.
19
	 */
20
	public const ASC = 'ASC';
21
22
	/**
23
	 * Sorting order flag.
24
	 */
25
	public const DESC = 'DESC';
26
27
	/**
28
	 * @var bool whether to turn on prepare emulation. Defaults to false, meaning PDO
29
	 *           will use the native prepare support if available. For some databases (such as MySQL),
30
	 *           this may need to be set true so that PDO can emulate the prepare support to bypass
31
	 *           the buggy native prepare support.
32
	 *           The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed
33
	 */
34
	public $emulatePrepare = false;
35
36
	/**
37
	 * @var \App\Db Table of connections with database
38
	 */
39
	private static $cache = [];
40
41
	/**
42
	 * @var array Configuration with database
43
	 */
44
	private static $config = [];
45
46
	/**
47
	 * @var bool Enable caching database instance
48
	 */
49
	public static $connectCache = false;
50
51
	/**
52
	 * @var string Database Name
53
	 */
54
	public $dbName;
55
56
	/**
57
	 * @var string Database section
58
	 */
59
	public $dbType;
60
61
	/**
62
	 * @var string Host database server
63
	 */
64
	public $host;
65
66
	/**
67
	 * @var int Port database server
68
	 */
69
	public $port;
70
71
	/** {@inheritdoc} */
72
	public $schemaMap = [
73
		'pgsql' => 'App\Db\Drivers\Pgsql\Schema', // PostgreSQL
74
		'mysqli' => 'yii\db\mysql\Schema', // MySQL
75
		'mysql' => 'App\Db\Drivers\Mysql\Schema', // MySQL
76
		'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
77
		'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
78
		'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts
79
		'oci' => 'yii\db\oci\Schema', // Oracle driver
80
		'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts
81
		'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts
82
		'cubrid' => 'yii\db\cubrid\Schema', // CUBRID
83
	];
84
85
	/**
86
	 * @var string the class used to create new database [[Command]] objects. If you want to extend the [[Command]] class,
87
	 *             you may configure this property to use your extended version of the class
88
	 */
89
	public $commandClass = '\App\Db\Command';
90
91
	/**
92
	 * @var Cache|string the cache object or the ID of the cache application component that
93
	 *                   is used to cache the table metadata
94
	 *
95 6356
	 * @see enableSchemaCache
96
	 */
97 6356
	public $schemaCache = false;
98 6356
99
	/**
100 3
	 * Creates the Db connection instance.
101 3
	 *
102 3
	 * @param string $type Name of database connection
103 3
	 *
104
	 * @return \App\Db
105
	 */
106
	public static function getInstance($type = 'base')
107
	{
108
		if (isset(self::$cache[$type])) {
109
			return self::$cache[$type];
110
		}
111
		$db = new self(self::getConfig($type));
112
		$db->dbType = $type;
113 4
		self::$cache[$type] = $db;
114
		return $db;
115 4
	}
116 3
117
	/**
118 4
	 * Load database connection configuration.
119
	 *
120
	 * @param string $type
121
	 *
122
	 * @return array with database configuration
123
	 */
124
	public static function getConfig(string $type)
125
	{
126
		if (!isset(self::$config[$type])) {
127
			self::$config[$type] = Config::db($type) ?? Config::db('base');
128
		}
129
		return self::$config[$type];
130
	}
131
132
	/**
133
	 * Set database connection configuration.
134
	 *
135
	 * @param array  $config
136
	 * @param string $type
137 2
	 */
138
	public static function setConfig($config, $type = 'base')
139 2
	{
140 2
		self::$config[$type] = $config;
141 2
	}
142 2
143 2
	/**
144 2
	 * Get info database server.
145 2
	 *
146
	 * @return array
147
	 */
148 2
	public function getInfo()
149
	{
150
		$pdo = $this->getSlavePdo();
151 2
		$statement = $pdo->prepare('SHOW VARIABLES');
152 2
		$statement->execute();
153 2
		$conf = $statement->fetchAll(\PDO::FETCH_KEY_PAIR);
154 2
		$statement = $pdo->prepare('SHOW STATUS');
155 2
		$statement->execute();
156 2
		$conf = array_merge($conf, $statement->fetchAll(\PDO::FETCH_KEY_PAIR));
157 2
		$statement = $pdo->prepare('SELECT VERSION()');
158
		$statement->execute();
159
		$fullVersion = $statement->fetch(\PDO::FETCH_COLUMN);
160
		[$version] = explode('-', $conf['version']);
161
		$conf['version_comment'] = $conf['version_comment'] . '|' . $fullVersion;
162
		$typeDb = 'MySQL';
163
		if (false !== stripos($conf['version_comment'], 'MariaDb')) {
164
			$typeDb = 'MariaDb';
165
		}
166
		$memory = $conf['key_buffer_size'] + ($conf['query_cache_size'] ?? 0) + $conf['tmp_table_size'] + $conf['innodb_buffer_pool_size'] +
167
		($conf['innodb_additional_mem_pool_size'] ?? 0) + $conf['innodb_log_buffer_size'] + ($conf['max_connections'] * ($conf['sort_buffer_size']
168
				+ $conf['read_buffer_size'] + $conf['read_rnd_buffer_size'] + $conf['join_buffer_size'] + $conf['thread_stack'] + $conf['binlog_cache_size']));
169
		return \array_merge($conf, [
170
			'driver' => $this->getDriverName(),
171
			'typeDb' => $typeDb,
172 6352
			'serverVersion' => $version,
173
			'maximumMemorySize' => $memory,
174 6352
			'clientVersion' => $pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION),
175
			'connectionStatus' => $pdo->getAttribute(\PDO::ATTR_CONNECTION_STATUS),
176
			'serverInfo' => $pdo->getAttribute(\PDO::ATTR_SERVER_INFO),
177
		]);
178
	}
179
180
	/**
181
	 * Get database info.
182
	 *
183
	 * @return array
184
	 */
185
	public function getDbInfo(): array
186 75
	{
187
		$return = [
188 75
			'isFileSize' => false,
189
			'size' => 0,
190
			'dataSize' => 0,
191
			'indexSize' => 0,
192
			'filesSize' => 0,
193
			'tables' => [],
194
		];
195
		$statement = $this->getSlavePdo()->prepare("SHOW TABLE STATUS FROM `{$this->dbName}`");
196
		$statement->execute();
197
		while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
198
			$return['tables'][$row['Name']] = [
199 3
				'rows' => $row['Rows'],
200
				'format' => $row['Row_format'],
201 3
				'engine' => $row['Engine'],
202
				'dataSize' => $row['Data_length'],
203
				'indexSize' => $row['Index_length'],
204
				'collation' => $row['Collation'],
205
			];
206
			$return['dataSize'] += $row['Data_length'];
207 3
			$return['indexSize'] += $row['Index_length'];
208
			$return['size'] += $row['Data_length'] += $row['Index_length'];
209
		}
210
		try {
211
			$statement = $this->getSlavePdo()->prepare("SELECT * FROM `information_schema`.`INNODB_SYS_TABLESPACES` WHERE `NAME` LIKE '{$this->dbName}/%'");
212
			$statement->execute();
213
			while ($row = $statement->fetch(\PDO::FETCH_ASSOC)) {
214
				$tableName = str_replace($this->dbName . '/', '', $row['NAME']);
215
				if (!empty($row['ALLOCATED_SIZE'])) {
216
					if (isset($return['tables'][$tableName])) {
217
						$return['tables'][$tableName]['fileSize'] = $row['ALLOCATED_SIZE'];
218
						$return['isFileSize'] = true;
219 11
					}
220
					$return['filesSize'] += $row['ALLOCATED_SIZE'];
221 11
				}
222 7
			}
223 7
		} catch (\Throwable $th) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
224 7
		}
225 7
		return $return;
226 7
	}
227 7
228
	/**
229 4
	 * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
230 4
	 * Tokens enclosed within double curly brackets are treated as table names, while
231 4
	 * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
232 4
	 * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
233
	 * with [[tablePrefix]].
234 11
	 *
235
	 * @param string $sql the SQL to be quoted
236
	 *
237
	 * @return string the quoted SQL
238
	 */
239
	public function quoteSql($sql)
240
	{
241
		return str_replace('#__', $this->tablePrefix, $sql);
242
	}
243
244 10
	/**
245
	 * Returns the ID of the last inserted row or sequence value.
246 10
	 *
247
	 * @param string $sequenceName name of the sequence object (required by some DBMS) ex. table vtiger_picklist >>> vtiger_picklist_picklistid_seq
248
	 *
249
	 * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
250
	 *
251
	 * @see https://www.php.net/manual/en/function.PDO-lastInsertId.php
252
	 */
253
	public function getLastInsertID($sequenceName = '')
254
	{
255
		return parent::getLastInsertID(str_replace('#__', $this->tablePrefix, $sequenceName));
256
	}
257 4
258
	/**
259 4
	 * Creates the PDO instance.
260 4
	 * This method is called by [[open]] to establish a DB connection.
261 4
	 * The default implementation will create a PHP PDO instance.
262
	 * You may override this method if the default PDO needs to be adapted for certain DBMS.
263 4
	 *
264 4
	 * @return PDO the pdo instance
0 ignored issues
show
Bug introduced by
The type App\PDO was not found. Did you mean PDO? If so, make sure to prefix the type with \.
Loading history...
265
	 */
266
	protected function createPdoInstance()
267
	{
268
		if (Debuger::isDebugBar()) {
269
			$bebugBar = Debuger::getDebugBar();
270
			$pdo = new Debug\DebugBar\TraceablePDO(parent::createPdoInstance());
271
			if ($bebugBar->hasCollector('pdo')) {
272
				$pdoCollector = $bebugBar->getCollector('pdo');
273
				$pdoCollector->addConnection($pdo, $this->dbType);
0 ignored issues
show
Bug introduced by
The method addConnection() does not exist on DebugBar\DataCollector\DataCollectorInterface. It seems like you code against a sub-type of DebugBar\DataCollector\DataCollectorInterface such as DebugBar\DataCollector\PDO\PDOCollector. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

273
				$pdoCollector->/** @scrutinizer ignore-call */ 
274
                   addConnection($pdo, $this->dbType);
Loading history...
274
			} else {
275
				$pdoCollector = new \DebugBar\DataCollector\PDO\PDOCollector();
276
				$pdoCollector->addConnection($pdo, $this->dbType);
277
				$bebugBar->addCollector($pdoCollector);
278
			}
279
			return $pdo;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $pdo returns the type App\Debug\DebugBar\TraceablePDO which is incompatible with the documented return type App\PDO.
Loading history...
280
		}
281
		return parent::createPdoInstance();
0 ignored issues
show
Bug Best Practice introduced by
The expression return parent::createPdoInstance() returns the type PDO which is incompatible with the documented return type App\PDO.
Loading history...
282
	}
283
284
	/**
285
	 * Get table unique ID. Temporary function.
286
	 *
287
	 * @param string       $tableName
288
	 * @param false|string $columnName
289
	 * @param bool         $seq
290
	 *
291
	 * @return int
292
	 */
293
	public function getUniqueID($tableName, $columnName = false, $seq = true)
294
	{
295
		if ($seq) {
296
			$tableName .= '_seq';
297
			$id = (new \App\Db\Query())->from($tableName)->scalar($this);
298
			++$id;
299
			$this->createCommand()->update($tableName, [
300
				'id' => $id,
301
			])->execute();
302
		} else {
303
			$id = (new \App\Db\Query())
304
				->from($tableName)
305
				->max($columnName, $this);
306
			++$id;
307
		}
308
		return $id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $id also could return the type false|string which is incompatible with the documented return type integer.
Loading history...
309
	}
310
311
	/**
312
	 * Check if table is present in database.
313
	 *
314
	 * @param string $tableName
315
	 *
316
	 * @return bool
317
	 */
318
	public function isTableExists($tableName)
319
	{
320
		return \in_array(str_replace('#__', $this->tablePrefix, $tableName), $this->getSchema()->getTableNames());
321
	}
322
323
	/**
324
	 * Creating a new DB table.
325
	 *
326
	 * @param string $tableName
327
	 * @param mixed  $columns
328
	 *
329
	 * @return bool
330
	 */
331
	public function createTable($tableName, $columns)
332
	{
333
		$tableOptions = null;
334
		if ('mysql' === $this->getDriverName()) {
335
			$tableOptions = 'CHARACTER SET utf8 ENGINE=InnoDB';
336
		}
337
		$this->createCommand()->createTable($tableName, $columns, $tableOptions)->execute();
338
	}
339
340
	/**
341
	 * Get table keys.
342
	 *
343
	 * @param string $tableName
344
	 *
345
	 * @return array
346
	 */
347
	public function getTableKeys($tableName)
348
	{
349
		if (Cache::has('getTableKeys', $tableName)) {
350
			return Cache::get('getTableKeys', $tableName);
351
		}
352
		if (!$this->isTableExists($tableName)) {
353
			return [];
354
		}
355
		$tableName = $this->quoteTableName(str_replace('#__', $this->tablePrefix, $tableName));
356
		$keys = [];
357
		if ('mysql' === $this->getDriverName()) {
358
			$dataReader = $this->createCommand()->setSql('SHOW KEYS FROM ' . $tableName)->query();
359
			while ($row = $dataReader->read()) {
360
				$keys[$row['Key_name']][$row['Column_name']] = ['columnName' => $row['Column_name'], 'unique' => empty($row['Non_unique'])];
361
			}
362
		}
363
		Cache::save('getTableKeys', $tableName, $keys, Cache::LONG);
364
		return $keys;
365
	}
366
367
	/**
368
	 * Get table primary keys.
369
	 *
370
	 * @param type $tableName
0 ignored issues
show
Bug introduced by
The type App\type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
371
	 *
372
	 * @return type
373
	 */
374
	public function getPrimaryKey($tableName)
375
	{
376
		if (Cache::has('getPrimaryKey', $tableName)) {
377
			return Cache::get('getPrimaryKey', $tableName);
378
		}
379
		$key = [];
380
		if ('mysql' === $this->getDriverName()) {
381
			$tableKeys = $this->getTableKeys($tableName);
382
			$key = isset($tableKeys['PRIMARY']) ? ['PRIMARY' => array_keys($tableKeys['PRIMARY'])] : [];
383
		}
384
		Cache::save('getPrimaryKey', $tableName, $key, Cache::LONG);
385
		return $key;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $key returns the type array|array<string,array> which is incompatible with the documented return type App\type.
Loading history...
386
	}
387
}
388