Completed
Push — master ( 1d5850...f37f83 )
by Andrii
08:50
created

Schema   F

Complexity

Total Complexity 108

Size/Duplication

Total Lines 818
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 10

Test Coverage

Coverage 8.45%

Importance

Changes 0
Metric Value
wmc 108
lcom 1
cbo 10
dl 0
loc 818
ccs 18
cts 213
cp 0.0845
rs 1.782
c 0
b 0
f 0

44 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A resolveTableName() 0 4 1
A findSchemaNames() 0 4 1
A findTableNames() 0 4 1
loadTableSchema() 0 1 ?
A createColumnSchema() 0 4 1
A getTableSchema() 0 4 1
A getTableSchemas() 0 4 1
A getSchemaNames() 0 8 3
A getTableNames() 0 8 3
A getQueryBuilder() 0 8 2
A getPdoType() 0 14 1
A refresh() 0 10 4
A refreshTableSchema() 0 11 4
A createQueryBuilder() 0 4 1
A createColumnSchemaBuilder() 0 4 1
A findUniqueIndexes() 0 4 1
A getLastInsertID() 0 8 3
A supportsSavepoint() 0 4 1
A createSavepoint() 0 4 1
A releaseSavepoint() 0 4 1
A rollBackSavepoint() 0 4 1
A setTransactionIsolationLevel() 0 4 1
A insert() 0 19 4
A quoteValue() 0 13 3
A quoteTableName() 0 15 5
A quoteColumnName() 0 17 5
A quoteSimpleTableName() 0 9 3
A quoteSimpleColumnName() 0 9 4
A unquoteSimpleTableName() 0 9 3
A unquoteSimpleColumnName() 0 9 3
A getRawTableName() 0 10 2
B getColumnPhpType() 0 26 8
A convertException() 0 16 5
A isReadQuery() 0 5 1
A getServerVersion() 0 7 2
A getCacheKey() 0 9 1
A getCacheTag() 0 8 1
B getTableMetadata() 0 20 8
A getSchemaMetadata() 0 16 4
A setTableMetadata() 0 4 1
A normalizePdoRowKeyCase() 0 14 3
A loadTableMetadataFromCache() 0 16 5
A saveTableMetadataToCache() 0 15 2

How to fix   Complexity   

Complex Class

Complex classes like Schema often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Schema, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use yii\base\BaseObject;
11
use yii\exceptions\InvalidCallException;
12
use yii\exceptions\InvalidConfigException;
13
use yii\exceptions\NotSupportedException;
14
use yii\caching\Cache;
15
use yii\caching\CacheInterface;
16
use yii\caching\TagDependency;
17
use yii\helpers\StringHelper;
18
use yii\helpers\Yii;
19
20
/**
21
 * Schema is the base class for concrete DBMS-specific schema classes.
22
 *
23
 * Schema represents the database schema information that is DBMS specific.
24
 *
25
 * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
26
 * sequence object. This property is read-only.
27
 * @property QueryBuilder $queryBuilder The query builder for this connection. This property is read-only.
28
 * @property string[] $schemaNames All schema names in the database, except system schemas. This property is
29
 * read-only.
30
 * @property string $serverVersion Server version as a string. This property is read-only.
31
 * @property string[] $tableNames All table names in the database. This property is read-only.
32
 * @property TableSchema[] $tableSchemas The metadata for all tables in the database. Each array element is an
33
 * instance of [[TableSchema]] or its child class. This property is read-only.
34
 * @property string $transactionIsolationLevel The transaction isolation level to use for this transaction.
35
 * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]],
36
 * [[Transaction::REPEATABLE_READ]] and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific
37
 * syntax to be used after `SET TRANSACTION ISOLATION LEVEL`. This property is write-only.
38
 *
39
 * @author Qiang Xue <[email protected]>
40
 * @author Sergey Makinen <[email protected]>
41
 * @since 2.0
42
 */
43
abstract class Schema extends BaseObject
44
{
45
    // The following are the supported abstract column data types.
46
    const TYPE_PK = 'pk';
47
    const TYPE_UPK = 'upk';
48
    const TYPE_BIGPK = 'bigpk';
49
    const TYPE_UBIGPK = 'ubigpk';
50
    const TYPE_CHAR = 'char';
51
    const TYPE_STRING = 'string';
52
    const TYPE_TEXT = 'text';
53
    const TYPE_TINYINT = 'tinyint';
54
    const TYPE_SMALLINT = 'smallint';
55
    const TYPE_INTEGER = 'integer';
56
    const TYPE_BIGINT = 'bigint';
57
    const TYPE_FLOAT = 'float';
58
    const TYPE_DOUBLE = 'double';
59
    const TYPE_DECIMAL = 'decimal';
60
    const TYPE_DATETIME = 'datetime';
61
    const TYPE_TIMESTAMP = 'timestamp';
62
    const TYPE_TIME = 'time';
63
    const TYPE_DATE = 'date';
64
    const TYPE_BINARY = 'binary';
65
    const TYPE_BOOLEAN = 'boolean';
66
    const TYPE_MONEY = 'money';
67
    const TYPE_JSON = 'json';
68
    /**
69
     * Schema cache version, to detect incompatibilities in cached values when the
70
     * data format of the cache changes.
71
     */
72
    const SCHEMA_CACHE_VERSION = 1;
73
74
    /**
75
     * @var Connection the database connection
76
     */
77
    public $db;
78
    /**
79
     * @var string the default schema name used for the current session.
80
     */
81
    public $defaultSchema;
82
    /**
83
     * @var array map of DB errors and corresponding exceptions
84
     * If left part is found in DB error message exception class from the right part is used.
85
     */
86
    public $exceptionMap = [
87
        'SQLSTATE[23' => IntegrityException::class,
88
    ];
89
    /**
90
     * @var string|array column schema class or class config
91
     * @since 2.0.11
92
     */
93
    public $columnSchemaClass = ColumnSchema::class;
94
95
    /**
96
     * @var string|string[] character used to quote schema, table, etc. names.
97
     * An array of 2 characters can be used in case starting and ending characters are different.
98
     * @since 2.0.14
99
     */
100
    protected $tableQuoteCharacter = "'";
101
    /**
102
     * @var string|string[] character used to quote column names.
103
     * An array of 2 characters can be used in case starting and ending characters are different.
104
     * @since 2.0.14
105
     */
106
    protected $columnQuoteCharacter = '"';
107
108
    /**
109
     * @var array list of ALL schema names in the database, except system schemas
110
     */
111
    private $_schemaNames;
112
    /**
113
     * @var array list of ALL table names in the database
114
     */
115
    private $_tableNames = [];
116
    /**
117
     * @var array list of loaded table metadata (table name => metadata type => metadata).
118
     */
119
    private $_tableMetadata = [];
120
    /**
121
     * @var QueryBuilder the query builder for this database
122
     */
123
    private $_builder;
124
    /**
125
     * @var string server version as a string.
126
     */
127
    private $_serverVersion;
128
129
130 3
    public function __construct(Connection $db)
131
    {
132 3
        $this->db = $db;
133
    }
134
135
    /**
136
     * Resolves the table name and schema name (if any).
137
     * @param string $name the table name
138
     * @return TableSchema [[TableSchema]] with resolved table, schema, etc. names.
139
     * @throws NotSupportedException if this method is not supported by the DBMS.
140
     * @since 2.0.13
141
     */
142
    protected function resolveTableName($name)
0 ignored issues
show
Unused Code introduced by
The parameter $name is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
143
    {
144
        throw new NotSupportedException(get_class($this) . ' does not support resolving table names.');
145
    }
146
147
    /**
148
     * Returns all schema names in the database, including the default one but not system schemas.
149
     * This method should be overridden by child classes in order to support this feature
150
     * because the default implementation simply throws an exception.
151
     * @return array all schema names in the database, except system schemas.
152
     * @throws NotSupportedException if this method is not supported by the DBMS.
153
     * @since 2.0.4
154
     */
155
    protected function findSchemaNames()
156
    {
157
        throw new NotSupportedException(get_class($this) . ' does not support fetching all schema names.');
158
    }
159
160
    /**
161
     * Returns all table names in the database.
162
     * This method should be overridden by child classes in order to support this feature
163
     * because the default implementation simply throws an exception.
164
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
165
     * @return array all table names in the database. The names have NO schema name prefix.
166
     * @throws NotSupportedException if this method is not supported by the DBMS.
167
     */
168
    protected function findTableNames($schema = '')
0 ignored issues
show
Unused Code introduced by
The parameter $schema is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
169
    {
170
        throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.');
171
    }
172
173
    /**
174
     * Loads the metadata for the specified table.
175
     * @param string $name table name
176
     * @return TableSchema|null DBMS-dependent table metadata, `null` if the table does not exist.
177
     */
178
    abstract protected function loadTableSchema($name);
179
180
    /**
181
     * Creates a column schema for the database.
182
     * This method may be overridden by child classes to create a DBMS-specific column schema.
183
     * @return ColumnSchema column schema instance.
184
     * @throws InvalidConfigException if a column schema class cannot be created.
185
     */
186
    protected function createColumnSchema()
187
    {
188
        return Yii::createObject($this->columnSchemaClass);
189
    }
190
191
    /**
192
     * Obtains the metadata for the named table.
193
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
194
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
195
     * @return TableSchema|null table metadata. `null` if the named table does not exist.
196
     */
197
    public function getTableSchema($name, $refresh = false)
198
    {
199
        return $this->getTableMetadata($name, 'schema', $refresh);
200
    }
201
202
    /**
203
     * Returns the metadata for all tables in the database.
204
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
205
     * @param bool $refresh whether to fetch the latest available table schemas. If this is `false`,
206
     * cached data may be returned if available.
207
     * @return TableSchema[] the metadata for all tables in the database.
208
     * Each array element is an instance of [[TableSchema]] or its child class.
209
     */
210
    public function getTableSchemas($schema = '', $refresh = false)
211
    {
212
        return $this->getSchemaMetadata($schema, 'schema', $refresh);
213
    }
214
215
    /**
216
     * Returns all schema names in the database, except system schemas.
217
     * @param bool $refresh whether to fetch the latest available schema names. If this is false,
218
     * schema names fetched previously (if available) will be returned.
219
     * @return string[] all schema names in the database, except system schemas.
220
     * @since 2.0.4
221
     */
222
    public function getSchemaNames($refresh = false)
223
    {
224
        if ($this->_schemaNames === null || $refresh) {
225
            $this->_schemaNames = $this->findSchemaNames();
226
        }
227
228
        return $this->_schemaNames;
229
    }
230
231
    /**
232
     * Returns all table names in the database.
233
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
234
     * If not empty, the returned table names will be prefixed with the schema name.
235
     * @param bool $refresh whether to fetch the latest available table names. If this is false,
236
     * table names fetched previously (if available) will be returned.
237
     * @return string[] all table names in the database.
238
     */
239
    public function getTableNames($schema = '', $refresh = false)
240
    {
241
        if (!isset($this->_tableNames[$schema]) || $refresh) {
242
            $this->_tableNames[$schema] = $this->findTableNames($schema);
243
        }
244
245
        return $this->_tableNames[$schema];
246
    }
247
248
    /**
249
     * @return QueryBuilder the query builder for this connection.
250
     */
251 3
    public function getQueryBuilder()
252
    {
253 3
        if ($this->_builder === null) {
254 3
            $this->_builder = $this->createQueryBuilder();
255
        }
256
257 3
        return $this->_builder;
258
    }
259
260
    /**
261
     * Determines the PDO type for the given PHP data value.
262
     * @param mixed $data the data whose PDO type is to be determined
263
     * @return int the PDO type
264
     * @see http://www.php.net/manual/en/pdo.constants.php
265
     */
266 1
    public function getPdoType($data)
267
    {
268 1
        static $typeMap = [
269
            // php type => PDO type
270
            'boolean' => \PDO::PARAM_BOOL,
271
            'integer' => \PDO::PARAM_INT,
272
            'string' => \PDO::PARAM_STR,
273
            'resource' => \PDO::PARAM_LOB,
274
            'NULL' => \PDO::PARAM_NULL,
275
        ];
276 1
        $type = gettype($data);
277
278 1
        return $typeMap[$type] ?? \PDO::PARAM_STR;
279
    }
280
281
    /**
282
     * Refreshes the schema.
283
     * This method cleans up all cached table schemas so that they can be re-created later
284
     * to reflect the database schema change.
285
     */
286
    public function refresh()
287
    {
288
        /* @var $cache CacheInterface */
289
        $cache = is_string($this->db->schemaCache) ? Yii::getApp()->get($this->db->schemaCache, false) : $this->db->schemaCache;
0 ignored issues
show
Deprecated Code introduced by
The method yii\helpers\BaseYii::getApp() has been deprecated with message: 3.0.0 Use DI instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
290
        if ($this->db->enableSchemaCache && $cache instanceof CacheInterface) {
0 ignored issues
show
Bug introduced by
The class yii\caching\CacheInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
291
            TagDependency::invalidate($cache, $this->getCacheTag());
292
        }
293
        $this->_tableNames = [];
294
        $this->_tableMetadata = [];
295
    }
296
297
    /**
298
     * Refreshes the particular table schema.
299
     * This method cleans up cached table schema so that it can be re-created later
300
     * to reflect the database schema change.
301
     * @param string $name table name.
302
     * @since 2.0.6
303
     */
304
    public function refreshTableSchema($name)
305
    {
306
        $rawName = $this->getRawTableName($name);
307
        unset($this->_tableMetadata[$rawName]);
308
        $this->_tableNames = [];
309
        /* @var $cache CacheInterface */
310
        $cache = is_string($this->db->schemaCache) ? Yii::getApp()->get($this->db->schemaCache, false) : $this->db->schemaCache;
0 ignored issues
show
Deprecated Code introduced by
The method yii\helpers\BaseYii::getApp() has been deprecated with message: 3.0.0 Use DI instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
311
        if ($this->db->enableSchemaCache && $cache instanceof CacheInterface) {
0 ignored issues
show
Bug introduced by
The class yii\caching\CacheInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
312
            $cache->delete($this->getCacheKey($rawName));
313
        }
314
    }
315
316
    /**
317
     * Creates a query builder for the database.
318
     * This method may be overridden by child classes to create a DBMS-specific query builder.
319
     * @return QueryBuilder query builder instance
320
     */
321
    public function createQueryBuilder()
322
    {
323
        return new QueryBuilder($this->db);
324
    }
325
326
    /**
327
     * Create a column schema builder instance giving the type and value precision.
328
     *
329
     * This method may be overridden by child classes to create a DBMS-specific column schema builder.
330
     *
331
     * @param string $type type of the column. See [[ColumnSchemaBuilder::$type]].
332
     * @param int|string|array $length length or precision of the column. See [[ColumnSchemaBuilder::$length]].
333
     * @return ColumnSchemaBuilder column schema builder instance
334
     * @since 2.0.6
335
     */
336
    public function createColumnSchemaBuilder($type, $length = null)
337
    {
338
        return new ColumnSchemaBuilder($type, $length);
339
    }
340
341
    /**
342
     * Returns all unique indexes for the given table.
343
     *
344
     * Each array element is of the following structure:
345
     *
346
     * ```php
347
     * [
348
     *  'IndexName1' => ['col1' [, ...]],
349
     *  'IndexName2' => ['col2' [, ...]],
350
     * ]
351
     * ```
352
     *
353
     * This method should be overridden by child classes in order to support this feature
354
     * because the default implementation simply throws an exception
355
     * @param TableSchema $table the table metadata
356
     * @return array all unique indexes for the given table.
357
     * @throws NotSupportedException if this method is called
358
     */
359
    public function findUniqueIndexes($table)
0 ignored issues
show
Unused Code introduced by
The parameter $table is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
360
    {
361
        throw new NotSupportedException(get_class($this) . ' does not support getting unique indexes information.');
362
    }
363
364
    /**
365
     * Returns the ID of the last inserted row or sequence value.
366
     * @param string $sequenceName name of the sequence object (required by some DBMS)
367
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
368
     * @throws InvalidCallException if the DB connection is not active
369
     * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
370
     */
371
    public function getLastInsertID($sequenceName = '')
372
    {
373
        if ($this->db->isActive) {
374
            return $this->db->pdo->lastInsertId($sequenceName === '' ? null : $this->quoteTableName($sequenceName));
375
        }
376
377
        throw new InvalidCallException('DB Connection is not active.');
378
    }
379
380
    /**
381
     * @return bool whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint).
382
     */
383
    public function supportsSavepoint()
384
    {
385
        return $this->db->enableSavepoint;
386
    }
387
388
    /**
389
     * Creates a new savepoint.
390
     * @param string $name the savepoint name
391
     */
392
    public function createSavepoint($name)
393
    {
394
        $this->db->createCommand("SAVEPOINT $name")->execute();
395
    }
396
397
    /**
398
     * Releases an existing savepoint.
399
     * @param string $name the savepoint name
400
     */
401
    public function releaseSavepoint($name)
402
    {
403
        $this->db->createCommand("RELEASE SAVEPOINT $name")->execute();
404
    }
405
406
    /**
407
     * Rolls back to a previously created savepoint.
408
     * @param string $name the savepoint name
409
     */
410
    public function rollBackSavepoint($name)
411
    {
412
        $this->db->createCommand("ROLLBACK TO SAVEPOINT $name")->execute();
413
    }
414
415
    /**
416
     * Sets the isolation level of the current transaction.
417
     * @param string $level The transaction isolation level to use for this transaction.
418
     * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
419
     * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
420
     * after `SET TRANSACTION ISOLATION LEVEL`.
421
     * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
422
     */
423
    public function setTransactionIsolationLevel($level)
424
    {
425
        $this->db->createCommand("SET TRANSACTION ISOLATION LEVEL $level")->execute();
426
    }
427
428
    /**
429
     * Executes the INSERT command, returning primary key values.
430
     * @param string $table the table that new rows will be inserted into.
431
     * @param array $columns the column data (name => value) to be inserted into the table.
432
     * @return array|false primary key values or false if the command fails
433
     * @since 2.0.4
434
     */
435
    public function insert($table, $columns)
436
    {
437
        $command = $this->db->createCommand()->insert($table, $columns);
438
        if (!$command->execute()) {
439
            return false;
440
        }
441
        $tableSchema = $this->getTableSchema($table);
442
        $result = [];
443
        foreach ($tableSchema->primaryKey as $name) {
444
            if ($tableSchema->columns[$name]->autoIncrement) {
445
                $result[$name] = $this->getLastInsertID($tableSchema->sequenceName);
446
                break;
447
            }
448
449
            $result[$name] = $columns[$name] ?? $tableSchema->columns[$name]->defaultValue;
450
        }
451
452
        return $result;
453
    }
454
455
    /**
456
     * Quotes a string value for use in a query.
457
     * Note that if the parameter is not a string, it will be returned without change.
458
     * @param string $str string to be quoted
459
     * @return string the properly quoted string
460
     * @see http://www.php.net/manual/en/function.PDO-quote.php
461
     */
462
    public function quoteValue($str)
463
    {
464
        if (!is_string($str)) {
465
            return $str;
466
        }
467
468
        if (($value = $this->db->getSlavePdo()->quote($str)) !== false) {
469
            return $value;
470
        }
471
472
        // the driver doesn't support quote (e.g. oci)
473
        return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
474
    }
475
476
    /**
477
     * Quotes a table name for use in a query.
478
     * If the table name contains schema prefix, the prefix will also be properly quoted.
479
     * If the table name is already quoted or contains '(' or '{{',
480
     * then this method will do nothing.
481
     * @param string $name table name
482
     * @return string the properly quoted table name
483
     * @see quoteSimpleTableName()
484
     */
485 3
    public function quoteTableName($name)
486
    {
487 3
        if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
488
            return $name;
489
        }
490 3
        if (strpos($name, '.') === false) {
491 3
            return $this->quoteSimpleTableName($name);
492
        }
493
        $parts = explode('.', $name);
494
        foreach ($parts as $i => $part) {
495
            $parts[$i] = $this->quoteSimpleTableName($part);
496
        }
497
498
        return implode('.', $parts);
499
    }
500
501
    /**
502
     * Quotes a column name for use in a query.
503
     * If the column name contains prefix, the prefix will also be properly quoted.
504
     * If the column name is already quoted or contains '(', '[[' or '{{',
505
     * then this method will do nothing.
506
     * @param string $name column name
507
     * @return string the properly quoted column name
508
     * @see quoteSimpleColumnName()
509
     */
510
    public function quoteColumnName($name)
511
    {
512
        if (strpos($name, '(') !== false || strpos($name, '[[') !== false) {
513
            return $name;
514
        }
515
        if (($pos = strrpos($name, '.')) !== false) {
516
            $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
517
            $name = substr($name, $pos + 1);
518
        } else {
519
            $prefix = '';
520
        }
521
        if (strpos($name, '{{') !== false) {
522
            return $name;
523
        }
524
525
        return $prefix . $this->quoteSimpleColumnName($name);
526
    }
527
528
    /**
529
     * Quotes a simple table name for use in a query.
530
     * A simple table name should contain the table name only without any schema prefix.
531
     * If the table name is already quoted, this method will do nothing.
532
     * @param string $name table name
533
     * @return string the properly quoted table name
534
     */
535 3
    public function quoteSimpleTableName($name)
536
    {
537 3
        if (is_string($this->tableQuoteCharacter)) {
538 3
            $startingCharacter = $endingCharacter = $this->tableQuoteCharacter;
539
        } else {
540
            [$startingCharacter, $endingCharacter] = $this->tableQuoteCharacter;
0 ignored issues
show
Bug introduced by
The variable $startingCharacter seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $endingCharacter seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
541
        }
542 3
        return strpos($name, $startingCharacter) !== false ? $name : $startingCharacter . $name . $endingCharacter;
0 ignored issues
show
Bug introduced by
The variable $startingCharacter does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $endingCharacter does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
543
    }
544
545
    /**
546
     * Quotes a simple column name for use in a query.
547
     * A simple column name should contain the column name only without any prefix.
548
     * If the column name is already quoted or is the asterisk character '*', this method will do nothing.
549
     * @param string $name column name
550
     * @return string the properly quoted column name
551
     */
552
    public function quoteSimpleColumnName($name)
553
    {
554
        if (is_string($this->tableQuoteCharacter)) {
555
            $startingCharacter = $endingCharacter = $this->columnQuoteCharacter;
556
        } else {
557
            [$startingCharacter, $endingCharacter] = $this->columnQuoteCharacter;
0 ignored issues
show
Bug introduced by
The variable $startingCharacter seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $endingCharacter seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
558
        }
559
        return $name === '*' || strpos($name, $startingCharacter) !== false ? $name : $startingCharacter . $name . $endingCharacter;
0 ignored issues
show
Bug introduced by
The variable $startingCharacter does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
The variable $endingCharacter does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
560
    }
561
562
    /**
563
     * Unquotes a simple table name.
564
     * A simple table name should contain the table name only without any schema prefix.
565
     * If the table name is not quoted, this method will do nothing.
566
     * @param string $name table name.
567
     * @return string unquoted table name.
568
     * @since 2.0.14
569
     */
570
    public function unquoteSimpleTableName($name)
571
    {
572
        if (is_string($this->tableQuoteCharacter)) {
573
            $startingCharacter = $this->tableQuoteCharacter;
574
        } else {
575
            $startingCharacter = $this->tableQuoteCharacter[0];
576
        }
577
        return strpos($name, $startingCharacter) === false ? $name : substr($name, 1, -1);
578
    }
579
580
    /**
581
     * Unquotes a simple column name.
582
     * A simple column name should contain the column name only without any prefix.
583
     * If the column name is not quoted or is the asterisk character '*', this method will do nothing.
584
     * @param string $name column name.
585
     * @return string unquoted column name.
586
     * @since 2.0.14
587
     */
588
    public function unquoteSimpleColumnName($name)
589
    {
590
        if (is_string($this->columnQuoteCharacter)) {
591
            $startingCharacter = $this->columnQuoteCharacter;
592
        } else {
593
            $startingCharacter = $this->columnQuoteCharacter[0];
594
        }
595
        return strpos($name, $startingCharacter) === false ? $name : substr($name, 1, -1);
596
    }
597
598
    /**
599
     * Returns the actual name of a given table name.
600
     * This method will strip off curly brackets from the given table name
601
     * and replace the percentage character '%' with [[Connection::tablePrefix]].
602
     * @param string $name the table name to be converted
603
     * @return string the real name of the given table name
604
     */
605
    public function getRawTableName($name)
606
    {
607
        if (strpos($name, '{{') !== false) {
608
            $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
609
610
            return str_replace('%', $this->db->tablePrefix, $name);
611
        }
612
613
        return $name;
614
    }
615
616
    /**
617
     * Extracts the PHP type from abstract DB type.
618
     * @param ColumnSchema $column the column schema information
619
     * @return string PHP type name
620
     */
621
    protected function getColumnPhpType($column)
622
    {
623
        static $typeMap = [
624
            // abstract type => php type
625
            self::TYPE_TINYINT => 'integer',
626
            self::TYPE_SMALLINT => 'integer',
627
            self::TYPE_INTEGER => 'integer',
628
            self::TYPE_BIGINT => 'integer',
629
            self::TYPE_BOOLEAN => 'boolean',
630
            self::TYPE_FLOAT => 'double',
631
            self::TYPE_DOUBLE => 'double',
632
            self::TYPE_BINARY => 'resource',
633
            self::TYPE_JSON => 'array',
634
        ];
635
        if (isset($typeMap[$column->type])) {
636
            if ($column->type === 'bigint') {
637
                return PHP_INT_SIZE === 8 && !$column->unsigned ? 'integer' : 'string';
638
            } elseif ($column->type === 'integer') {
639
                return PHP_INT_SIZE === 4 && $column->unsigned ? 'string' : 'integer';
640
            }
641
642
            return $typeMap[$column->type];
643
        }
644
645
        return 'string';
646
    }
647
648
    /**
649
     * Converts a DB exception to a more concrete one if possible.
650
     *
651
     * @param \Exception $e
652
     * @param string $rawSql SQL that produced exception
653
     * @return Exception
654
     */
655
    public function convertException(\Exception $e, $rawSql)
656
    {
657
        if ($e instanceof Exception) {
658
            return $e;
659
        }
660
661
        $exceptionClass = Exception::class;
662
        foreach ($this->exceptionMap as $error => $class) {
663
            if (strpos($e->getMessage(), $error) !== false) {
664
                $exceptionClass = $class;
665
            }
666
        }
667
        $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
668
        $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
669
        return new $exceptionClass($message, $errorInfo, (int) $e->getCode(), $e);
670
    }
671
672
    /**
673
     * Returns a value indicating whether a SQL statement is for read purpose.
674
     * @param string $sql the SQL statement
675
     * @return bool whether a SQL statement is for read purpose.
676
     */
677
    public function isReadQuery($sql)
678
    {
679
        $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i';
680
        return preg_match($pattern, $sql) > 0;
681
    }
682
683
    /**
684
     * Returns a server version as a string comparable by [[\version_compare()]].
685
     * @return string server version as a string.
686
     * @since 2.0.14
687
     */
688
    public function getServerVersion()
689
    {
690
        if ($this->_serverVersion === null) {
691
            $this->_serverVersion = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
692
        }
693
        return $this->_serverVersion;
694
    }
695
696
    /**
697
     * Returns the cache key for the specified table name.
698
     * @param string $name the table name.
699
     * @return mixed the cache key.
700
     */
701
    protected function getCacheKey($name)
702
    {
703
        return [
704
            __CLASS__,
705
            $this->db->dsn,
706
            $this->db->username,
707
            $this->getRawTableName($name),
708
        ];
709
    }
710
711
    /**
712
     * Returns the cache tag name.
713
     * This allows [[refresh()]] to invalidate all cached table schemas.
714
     * @return string the cache tag name
715
     */
716
    protected function getCacheTag()
717
    {
718
        return md5(serialize([
719
            __CLASS__,
720
            $this->db->dsn,
721
            $this->db->username,
722
        ]));
723
    }
724
725
    /**
726
     * Returns the metadata of the given type for the given table.
727
     * If there's no metadata in the cache, this method will call
728
     * a `'loadTable' . ucfirst($type)` named method with the table name to obtain the metadata.
729
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
730
     * @param string $type metadata type.
731
     * @param bool $refresh whether to reload the table metadata even if it is found in the cache.
732
     * @return mixed metadata.
733
     * @since 2.0.13
734
     */
735
    protected function getTableMetadata($name, $type, $refresh)
736
    {
737
        $cache = null;
738
        if ($this->db->enableSchemaCache && !in_array($name, $this->db->schemaCacheExclude, true)) {
739
            $schemaCache = is_string($this->db->schemaCache) ? Yii::getApp()->get($this->db->schemaCache, false) : $this->db->schemaCache;
0 ignored issues
show
Deprecated Code introduced by
The method yii\helpers\BaseYii::getApp() has been deprecated with message: 3.0.0 Use DI instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
740
            if ($schemaCache instanceof CacheInterface) {
0 ignored issues
show
Bug introduced by
The class yii\caching\CacheInterface does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
741
                $cache = $schemaCache;
742
            }
743
        }
744
        $rawName = $this->getRawTableName($name);
745
        if (!isset($this->_tableMetadata[$rawName])) {
746
            $this->loadTableMetadataFromCache($cache, $rawName);
747
        }
748
        if ($refresh || !array_key_exists($type, $this->_tableMetadata[$rawName])) {
749
            $this->_tableMetadata[$rawName][$type] = $this->{'loadTable' . ucfirst($type)}($rawName);
750
            $this->saveTableMetadataToCache($cache, $rawName);
751
        }
752
753
        return $this->_tableMetadata[$rawName][$type];
754
    }
755
756
    /**
757
     * Returns the metadata of the given type for all tables in the given schema.
758
     * This method will call a `'getTable' . ucfirst($type)` named method with the table name
759
     * and the refresh flag to obtain the metadata.
760
     * @param string $schema the schema of the metadata. Defaults to empty string, meaning the current or default schema name.
761
     * @param string $type metadata type.
762
     * @param bool $refresh whether to fetch the latest available table metadata. If this is `false`,
763
     * cached data may be returned if available.
764
     * @return array array of metadata.
765
     * @since 2.0.13
766
     */
767
    protected function getSchemaMetadata($schema, $type, $refresh)
768
    {
769
        $metadata = [];
770
        $methodName = 'getTable' . ucfirst($type);
771
        foreach ($this->getTableNames($schema, $refresh) as $name) {
772
            if ($schema !== '') {
773
                $name = $schema . '.' . $name;
774
            }
775
            $tableMetadata = $this->$methodName($name, $refresh);
776
            if ($tableMetadata !== null) {
777
                $metadata[] = $tableMetadata;
778
            }
779
        }
780
781
        return $metadata;
782
    }
783
784
    /**
785
     * Sets the metadata of the given type for the given table.
786
     * @param string $name table name.
787
     * @param string $type metadata type.
788
     * @param mixed $data metadata.
789
     * @since 2.0.13
790
     */
791
    protected function setTableMetadata($name, $type, $data)
792
    {
793
        $this->_tableMetadata[$this->getRawTableName($name)][$type] = $data;
794
    }
795
796
    /**
797
     * Changes row's array key case to lower if PDO's one is set to uppercase.
798
     * @param array $row row's array or an array of row's arrays.
799
     * @param bool $multiple whether multiple rows or a single row passed.
800
     * @return array normalized row or rows.
801
     * @since 2.0.13
802
     */
803
    protected function normalizePdoRowKeyCase(array $row, $multiple)
804
    {
805
        if ($this->db->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_UPPER) {
806
            return $row;
807
        }
808
809
        if ($multiple) {
810
            return array_map(function (array $row) {
811
                return array_change_key_case($row, CASE_LOWER);
812
            }, $row);
813
        }
814
815
        return array_change_key_case($row, CASE_LOWER);
816
    }
817
818
    /**
819
     * Tries to load and populate table metadata from cache.
820
     * @param Cache|null $cache
821
     * @param string $name
822
     */
823
    private function loadTableMetadataFromCache($cache, $name)
824
    {
825
        if ($cache === null) {
826
            $this->_tableMetadata[$name] = [];
827
            return;
828
        }
829
830
        $metadata = $cache->get($this->getCacheKey($name));
831
        if (!is_array($metadata) || !isset($metadata['cacheVersion']) || $metadata['cacheVersion'] !== static::SCHEMA_CACHE_VERSION) {
832
            $this->_tableMetadata[$name] = [];
833
            return;
834
        }
835
836
        unset($metadata['cacheVersion']);
837
        $this->_tableMetadata[$name] = $metadata;
838
    }
839
840
    /**
841
     * Saves table metadata to cache.
842
     * @param Cache|null $cache
843
     * @param string $name
844
     */
845
    private function saveTableMetadataToCache($cache, $name)
846
    {
847
        if ($cache === null) {
848
            return;
849
        }
850
851
        $metadata = $this->_tableMetadata[$name];
852
        $metadata['cacheVersion'] = static::SCHEMA_CACHE_VERSION;
853
        $cache->set(
854
            $this->getCacheKey($name),
855
            $metadata,
856
            $this->db->schemaCacheDuration,
857
            new TagDependency(['tags' => $this->getCacheTag()])
858
        );
859
    }
860
}
861