Completed
Push — master ( 594ce5...8355c2 )
by Andrii
02:35
created

Schema::findTableNames()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
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
    /**
131
     * Resolves the table name and schema name (if any).
132
     * @param string $name the table name
133
     * @return TableSchema [[TableSchema]] with resolved table, schema, etc. names.
134
     * @throws NotSupportedException if this method is not supported by the DBMS.
135
     * @since 2.0.13
136
     */
137
    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...
138
    {
139
        throw new NotSupportedException(get_class($this) . ' does not support resolving table names.');
140
    }
141
142
    /**
143
     * Returns all schema names in the database, including the default one but not system schemas.
144
     * This method should be overridden by child classes in order to support this feature
145
     * because the default implementation simply throws an exception.
146
     * @return array all schema names in the database, except system schemas.
147
     * @throws NotSupportedException if this method is not supported by the DBMS.
148
     * @since 2.0.4
149
     */
150
    protected function findSchemaNames()
151
    {
152
        throw new NotSupportedException(get_class($this) . ' does not support fetching all schema names.');
153
    }
154
155
    /**
156
     * Returns all table names in the database.
157
     * This method should be overridden by child classes in order to support this feature
158
     * because the default implementation simply throws an exception.
159
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
160
     * @return array all table names in the database. The names have NO schema name prefix.
161
     * @throws NotSupportedException if this method is not supported by the DBMS.
162
     */
163
    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...
164
    {
165
        throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.');
166
    }
167
168
    /**
169
     * Loads the metadata for the specified table.
170
     * @param string $name table name
171
     * @return TableSchema|null DBMS-dependent table metadata, `null` if the table does not exist.
172
     */
173
    abstract protected function loadTableSchema($name);
174
175
    /**
176
     * Creates a column schema for the database.
177
     * This method may be overridden by child classes to create a DBMS-specific column schema.
178
     * @return ColumnSchema column schema instance.
179
     * @throws InvalidConfigException if a column schema class cannot be created.
180
     */
181
    protected function createColumnSchema()
182
    {
183
        return Yii::createObject($this->columnSchemaClass);
184
    }
185
186
    /**
187
     * Obtains the metadata for the named table.
188
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
189
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
190
     * @return TableSchema|null table metadata. `null` if the named table does not exist.
191
     */
192
    public function getTableSchema($name, $refresh = false)
193
    {
194
        return $this->getTableMetadata($name, 'schema', $refresh);
195
    }
196
197
    /**
198
     * Returns the metadata for all tables in the database.
199
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
200
     * @param bool $refresh whether to fetch the latest available table schemas. If this is `false`,
201
     * cached data may be returned if available.
202
     * @return TableSchema[] the metadata for all tables in the database.
203
     * Each array element is an instance of [[TableSchema]] or its child class.
204
     */
205
    public function getTableSchemas($schema = '', $refresh = false)
206
    {
207
        return $this->getSchemaMetadata($schema, 'schema', $refresh);
208
    }
209
210
    /**
211
     * Returns all schema names in the database, except system schemas.
212
     * @param bool $refresh whether to fetch the latest available schema names. If this is false,
213
     * schema names fetched previously (if available) will be returned.
214
     * @return string[] all schema names in the database, except system schemas.
215
     * @since 2.0.4
216
     */
217
    public function getSchemaNames($refresh = false)
218
    {
219
        if ($this->_schemaNames === null || $refresh) {
220
            $this->_schemaNames = $this->findSchemaNames();
221
        }
222
223
        return $this->_schemaNames;
224
    }
225
226
    /**
227
     * Returns all table names in the database.
228
     * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
229
     * If not empty, the returned table names will be prefixed with the schema name.
230
     * @param bool $refresh whether to fetch the latest available table names. If this is false,
231
     * table names fetched previously (if available) will be returned.
232
     * @return string[] all table names in the database.
233
     */
234
    public function getTableNames($schema = '', $refresh = false)
235
    {
236
        if (!isset($this->_tableNames[$schema]) || $refresh) {
237
            $this->_tableNames[$schema] = $this->findTableNames($schema);
238
        }
239
240
        return $this->_tableNames[$schema];
241
    }
242
243
    /**
244
     * @return QueryBuilder the query builder for this connection.
245
     */
246 3
    public function getQueryBuilder()
247
    {
248 3
        if ($this->_builder === null) {
249 3
            $this->_builder = $this->createQueryBuilder();
250
        }
251
252 3
        return $this->_builder;
253
    }
254
255
    /**
256
     * Determines the PDO type for the given PHP data value.
257
     * @param mixed $data the data whose PDO type is to be determined
258
     * @return int the PDO type
259
     * @see http://www.php.net/manual/en/pdo.constants.php
260
     */
261 1
    public function getPdoType($data)
262
    {
263 1
        static $typeMap = [
264
            // php type => PDO type
265
            'boolean' => \PDO::PARAM_BOOL,
266
            'integer' => \PDO::PARAM_INT,
267
            'string' => \PDO::PARAM_STR,
268
            'resource' => \PDO::PARAM_LOB,
269
            'NULL' => \PDO::PARAM_NULL,
270
        ];
271 1
        $type = gettype($data);
272
273 1
        return $typeMap[$type] ?? \PDO::PARAM_STR;
274
    }
275
276
    /**
277
     * Refreshes the schema.
278
     * This method cleans up all cached table schemas so that they can be re-created later
279
     * to reflect the database schema change.
280
     */
281
    public function refresh()
282
    {
283
        /* @var $cache CacheInterface */
284
        $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...
285
        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...
286
            TagDependency::invalidate($cache, $this->getCacheTag());
287
        }
288
        $this->_tableNames = [];
289
        $this->_tableMetadata = [];
290
    }
291
292
    /**
293
     * Refreshes the particular table schema.
294
     * This method cleans up cached table schema so that it can be re-created later
295
     * to reflect the database schema change.
296
     * @param string $name table name.
297
     * @since 2.0.6
298
     */
299
    public function refreshTableSchema($name)
300
    {
301
        $rawName = $this->getRawTableName($name);
302
        unset($this->_tableMetadata[$rawName]);
303
        $this->_tableNames = [];
304
        /* @var $cache CacheInterface */
305
        $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...
306
        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...
307
            $cache->delete($this->getCacheKey($rawName));
308
        }
309
    }
310
311
    /**
312
     * Creates a query builder for the database.
313
     * This method may be overridden by child classes to create a DBMS-specific query builder.
314
     * @return QueryBuilder query builder instance
315
     */
316
    public function createQueryBuilder()
317
    {
318
        return new QueryBuilder($this->db);
319
    }
320
321
    /**
322
     * Create a column schema builder instance giving the type and value precision.
323
     *
324
     * This method may be overridden by child classes to create a DBMS-specific column schema builder.
325
     *
326
     * @param string $type type of the column. See [[ColumnSchemaBuilder::$type]].
327
     * @param int|string|array $length length or precision of the column. See [[ColumnSchemaBuilder::$length]].
328
     * @return ColumnSchemaBuilder column schema builder instance
329
     * @since 2.0.6
330
     */
331
    public function createColumnSchemaBuilder($type, $length = null)
332
    {
333
        return new ColumnSchemaBuilder($type, $length);
334
    }
335
336
    /**
337
     * Returns all unique indexes for the given table.
338
     *
339
     * Each array element is of the following structure:
340
     *
341
     * ```php
342
     * [
343
     *  'IndexName1' => ['col1' [, ...]],
344
     *  'IndexName2' => ['col2' [, ...]],
345
     * ]
346
     * ```
347
     *
348
     * This method should be overridden by child classes in order to support this feature
349
     * because the default implementation simply throws an exception
350
     * @param TableSchema $table the table metadata
351
     * @return array all unique indexes for the given table.
352
     * @throws NotSupportedException if this method is called
353
     */
354
    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...
355
    {
356
        throw new NotSupportedException(get_class($this) . ' does not support getting unique indexes information.');
357
    }
358
359
    /**
360
     * Returns the ID of the last inserted row or sequence value.
361
     * @param string $sequenceName name of the sequence object (required by some DBMS)
362
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
363
     * @throws InvalidCallException if the DB connection is not active
364
     * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
365
     */
366
    public function getLastInsertID($sequenceName = '')
367
    {
368
        if ($this->db->isActive) {
369
            return $this->db->pdo->lastInsertId($sequenceName === '' ? null : $this->quoteTableName($sequenceName));
370
        }
371
372
        throw new InvalidCallException('DB Connection is not active.');
373
    }
374
375
    /**
376
     * @return bool whether this DBMS supports [savepoint](http://en.wikipedia.org/wiki/Savepoint).
377
     */
378
    public function supportsSavepoint()
379
    {
380
        return $this->db->enableSavepoint;
381
    }
382
383
    /**
384
     * Creates a new savepoint.
385
     * @param string $name the savepoint name
386
     */
387
    public function createSavepoint($name)
388
    {
389
        $this->db->createCommand("SAVEPOINT $name")->execute();
390
    }
391
392
    /**
393
     * Releases an existing savepoint.
394
     * @param string $name the savepoint name
395
     */
396
    public function releaseSavepoint($name)
397
    {
398
        $this->db->createCommand("RELEASE SAVEPOINT $name")->execute();
399
    }
400
401
    /**
402
     * Rolls back to a previously created savepoint.
403
     * @param string $name the savepoint name
404
     */
405
    public function rollBackSavepoint($name)
406
    {
407
        $this->db->createCommand("ROLLBACK TO SAVEPOINT $name")->execute();
408
    }
409
410
    /**
411
     * Sets the isolation level of the current transaction.
412
     * @param string $level The transaction isolation level to use for this transaction.
413
     * This can be one of [[Transaction::READ_UNCOMMITTED]], [[Transaction::READ_COMMITTED]], [[Transaction::REPEATABLE_READ]]
414
     * and [[Transaction::SERIALIZABLE]] but also a string containing DBMS specific syntax to be used
415
     * after `SET TRANSACTION ISOLATION LEVEL`.
416
     * @see http://en.wikipedia.org/wiki/Isolation_%28database_systems%29#Isolation_levels
417
     */
418
    public function setTransactionIsolationLevel($level)
419
    {
420
        $this->db->createCommand("SET TRANSACTION ISOLATION LEVEL $level")->execute();
421
    }
422
423
    /**
424
     * Executes the INSERT command, returning primary key values.
425
     * @param string $table the table that new rows will be inserted into.
426
     * @param array $columns the column data (name => value) to be inserted into the table.
427
     * @return array|false primary key values or false if the command fails
428
     * @since 2.0.4
429
     */
430
    public function insert($table, $columns)
431
    {
432
        $command = $this->db->createCommand()->insert($table, $columns);
433
        if (!$command->execute()) {
434
            return false;
435
        }
436
        $tableSchema = $this->getTableSchema($table);
437
        $result = [];
438
        foreach ($tableSchema->primaryKey as $name) {
439
            if ($tableSchema->columns[$name]->autoIncrement) {
440
                $result[$name] = $this->getLastInsertID($tableSchema->sequenceName);
441
                break;
442
            }
443
444
            $result[$name] = $columns[$name] ?? $tableSchema->columns[$name]->defaultValue;
445
        }
446
447
        return $result;
448
    }
449
450
    /**
451
     * Quotes a string value for use in a query.
452
     * Note that if the parameter is not a string, it will be returned without change.
453
     * @param string $str string to be quoted
454
     * @return string the properly quoted string
455
     * @see http://www.php.net/manual/en/function.PDO-quote.php
456
     */
457
    public function quoteValue($str)
458
    {
459
        if (!is_string($str)) {
460
            return $str;
461
        }
462
463
        if (($value = $this->db->getSlavePdo()->quote($str)) !== false) {
464
            return $value;
465
        }
466
467
        // the driver doesn't support quote (e.g. oci)
468
        return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
469
    }
470
471
    /**
472
     * Quotes a table name for use in a query.
473
     * If the table name contains schema prefix, the prefix will also be properly quoted.
474
     * If the table name is already quoted or contains '(' or '{{',
475
     * then this method will do nothing.
476
     * @param string $name table name
477
     * @return string the properly quoted table name
478
     * @see quoteSimpleTableName()
479
     */
480 3
    public function quoteTableName($name)
481
    {
482 3
        if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
483
            return $name;
484
        }
485 3
        if (strpos($name, '.') === false) {
486 3
            return $this->quoteSimpleTableName($name);
487
        }
488
        $parts = explode('.', $name);
489
        foreach ($parts as $i => $part) {
490
            $parts[$i] = $this->quoteSimpleTableName($part);
491
        }
492
493
        return implode('.', $parts);
494
    }
495
496
    /**
497
     * Quotes a column name for use in a query.
498
     * If the column name contains prefix, the prefix will also be properly quoted.
499
     * If the column name is already quoted or contains '(', '[[' or '{{',
500
     * then this method will do nothing.
501
     * @param string $name column name
502
     * @return string the properly quoted column name
503
     * @see quoteSimpleColumnName()
504
     */
505
    public function quoteColumnName($name)
506
    {
507
        if (strpos($name, '(') !== false || strpos($name, '[[') !== false) {
508
            return $name;
509
        }
510
        if (($pos = strrpos($name, '.')) !== false) {
511
            $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
512
            $name = substr($name, $pos + 1);
513
        } else {
514
            $prefix = '';
515
        }
516
        if (strpos($name, '{{') !== false) {
517
            return $name;
518
        }
519
520
        return $prefix . $this->quoteSimpleColumnName($name);
521
    }
522
523
    /**
524
     * Quotes a simple table name for use in a query.
525
     * A simple table name should contain the table name only without any schema prefix.
526
     * If the table name is already quoted, this method will do nothing.
527
     * @param string $name table name
528
     * @return string the properly quoted table name
529
     */
530 3
    public function quoteSimpleTableName($name)
531
    {
532 3
        if (is_string($this->tableQuoteCharacter)) {
533 3
            $startingCharacter = $endingCharacter = $this->tableQuoteCharacter;
534
        } else {
535
            [$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...
536
        }
537 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...
538
    }
539
540
    /**
541
     * Quotes a simple column name for use in a query.
542
     * A simple column name should contain the column name only without any prefix.
543
     * If the column name is already quoted or is the asterisk character '*', this method will do nothing.
544
     * @param string $name column name
545
     * @return string the properly quoted column name
546
     */
547
    public function quoteSimpleColumnName($name)
548
    {
549
        if (is_string($this->tableQuoteCharacter)) {
550
            $startingCharacter = $endingCharacter = $this->columnQuoteCharacter;
551
        } else {
552
            [$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...
553
        }
554
        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...
555
    }
556
557
    /**
558
     * Unquotes a simple table name.
559
     * A simple table name should contain the table name only without any schema prefix.
560
     * If the table name is not quoted, this method will do nothing.
561
     * @param string $name table name.
562
     * @return string unquoted table name.
563
     * @since 2.0.14
564
     */
565
    public function unquoteSimpleTableName($name)
566
    {
567
        if (is_string($this->tableQuoteCharacter)) {
568
            $startingCharacter = $this->tableQuoteCharacter;
569
        } else {
570
            $startingCharacter = $this->tableQuoteCharacter[0];
571
        }
572
        return strpos($name, $startingCharacter) === false ? $name : substr($name, 1, -1);
573
    }
574
575
    /**
576
     * Unquotes a simple column name.
577
     * A simple column name should contain the column name only without any prefix.
578
     * If the column name is not quoted or is the asterisk character '*', this method will do nothing.
579
     * @param string $name column name.
580
     * @return string unquoted column name.
581
     * @since 2.0.14
582
     */
583
    public function unquoteSimpleColumnName($name)
584
    {
585
        if (is_string($this->columnQuoteCharacter)) {
586
            $startingCharacter = $this->columnQuoteCharacter;
587
        } else {
588
            $startingCharacter = $this->columnQuoteCharacter[0];
589
        }
590
        return strpos($name, $startingCharacter) === false ? $name : substr($name, 1, -1);
591
    }
592
593
    /**
594
     * Returns the actual name of a given table name.
595
     * This method will strip off curly brackets from the given table name
596
     * and replace the percentage character '%' with [[Connection::tablePrefix]].
597
     * @param string $name the table name to be converted
598
     * @return string the real name of the given table name
599
     */
600
    public function getRawTableName($name)
601
    {
602
        if (strpos($name, '{{') !== false) {
603
            $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
604
605
            return str_replace('%', $this->db->tablePrefix, $name);
606
        }
607
608
        return $name;
609
    }
610
611
    /**
612
     * Extracts the PHP type from abstract DB type.
613
     * @param ColumnSchema $column the column schema information
614
     * @return string PHP type name
615
     */
616
    protected function getColumnPhpType($column)
617
    {
618
        static $typeMap = [
619
            // abstract type => php type
620
            self::TYPE_TINYINT => 'integer',
621
            self::TYPE_SMALLINT => 'integer',
622
            self::TYPE_INTEGER => 'integer',
623
            self::TYPE_BIGINT => 'integer',
624
            self::TYPE_BOOLEAN => 'boolean',
625
            self::TYPE_FLOAT => 'double',
626
            self::TYPE_DOUBLE => 'double',
627
            self::TYPE_BINARY => 'resource',
628
            self::TYPE_JSON => 'array',
629
        ];
630
        if (isset($typeMap[$column->type])) {
631
            if ($column->type === 'bigint') {
632
                return PHP_INT_SIZE === 8 && !$column->unsigned ? 'integer' : 'string';
633
            } elseif ($column->type === 'integer') {
634
                return PHP_INT_SIZE === 4 && $column->unsigned ? 'string' : 'integer';
635
            }
636
637
            return $typeMap[$column->type];
638
        }
639
640
        return 'string';
641
    }
642
643
    /**
644
     * Converts a DB exception to a more concrete one if possible.
645
     *
646
     * @param \Exception $e
647
     * @param string $rawSql SQL that produced exception
648
     * @return Exception
649
     */
650
    public function convertException(\Exception $e, $rawSql)
651
    {
652
        if ($e instanceof Exception) {
653
            return $e;
654
        }
655
656
        $exceptionClass = Exception::class;
657
        foreach ($this->exceptionMap as $error => $class) {
658
            if (strpos($e->getMessage(), $error) !== false) {
659
                $exceptionClass = $class;
660
            }
661
        }
662
        $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
663
        $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
664
        return new $exceptionClass($message, $errorInfo, (int) $e->getCode(), $e);
665
    }
666
667
    /**
668
     * Returns a value indicating whether a SQL statement is for read purpose.
669
     * @param string $sql the SQL statement
670
     * @return bool whether a SQL statement is for read purpose.
671
     */
672
    public function isReadQuery($sql)
673
    {
674
        $pattern = '/^\s*(SELECT|SHOW|DESCRIBE)\b/i';
675
        return preg_match($pattern, $sql) > 0;
676
    }
677
678
    /**
679
     * Returns a server version as a string comparable by [[\version_compare()]].
680
     * @return string server version as a string.
681
     * @since 2.0.14
682
     */
683
    public function getServerVersion()
684
    {
685
        if ($this->_serverVersion === null) {
686
            $this->_serverVersion = $this->db->getSlavePdo()->getAttribute(\PDO::ATTR_SERVER_VERSION);
687
        }
688
        return $this->_serverVersion;
689
    }
690
691
    /**
692
     * Returns the cache key for the specified table name.
693
     * @param string $name the table name.
694
     * @return mixed the cache key.
695
     */
696
    protected function getCacheKey($name)
697
    {
698
        return [
699
            __CLASS__,
700
            $this->db->dsn,
701
            $this->db->username,
702
            $this->getRawTableName($name),
703
        ];
704
    }
705
706
    /**
707
     * Returns the cache tag name.
708
     * This allows [[refresh()]] to invalidate all cached table schemas.
709
     * @return string the cache tag name
710
     */
711
    protected function getCacheTag()
712
    {
713
        return md5(serialize([
714
            __CLASS__,
715
            $this->db->dsn,
716
            $this->db->username,
717
        ]));
718
    }
719
720
    /**
721
     * Returns the metadata of the given type for the given table.
722
     * If there's no metadata in the cache, this method will call
723
     * a `'loadTable' . ucfirst($type)` named method with the table name to obtain the metadata.
724
     * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
725
     * @param string $type metadata type.
726
     * @param bool $refresh whether to reload the table metadata even if it is found in the cache.
727
     * @return mixed metadata.
728
     * @since 2.0.13
729
     */
730
    protected function getTableMetadata($name, $type, $refresh)
731
    {
732
        $cache = null;
733
        if ($this->db->enableSchemaCache && !in_array($name, $this->db->schemaCacheExclude, true)) {
734
            $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...
735
            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...
736
                $cache = $schemaCache;
737
            }
738
        }
739
        $rawName = $this->getRawTableName($name);
740
        if (!isset($this->_tableMetadata[$rawName])) {
741
            $this->loadTableMetadataFromCache($cache, $rawName);
742
        }
743
        if ($refresh || !array_key_exists($type, $this->_tableMetadata[$rawName])) {
744
            $this->_tableMetadata[$rawName][$type] = $this->{'loadTable' . ucfirst($type)}($rawName);
745
            $this->saveTableMetadataToCache($cache, $rawName);
746
        }
747
748
        return $this->_tableMetadata[$rawName][$type];
749
    }
750
751
    /**
752
     * Returns the metadata of the given type for all tables in the given schema.
753
     * This method will call a `'getTable' . ucfirst($type)` named method with the table name
754
     * and the refresh flag to obtain the metadata.
755
     * @param string $schema the schema of the metadata. Defaults to empty string, meaning the current or default schema name.
756
     * @param string $type metadata type.
757
     * @param bool $refresh whether to fetch the latest available table metadata. If this is `false`,
758
     * cached data may be returned if available.
759
     * @return array array of metadata.
760
     * @since 2.0.13
761
     */
762
    protected function getSchemaMetadata($schema, $type, $refresh)
763
    {
764
        $metadata = [];
765
        $methodName = 'getTable' . ucfirst($type);
766
        foreach ($this->getTableNames($schema, $refresh) as $name) {
767
            if ($schema !== '') {
768
                $name = $schema . '.' . $name;
769
            }
770
            $tableMetadata = $this->$methodName($name, $refresh);
771
            if ($tableMetadata !== null) {
772
                $metadata[] = $tableMetadata;
773
            }
774
        }
775
776
        return $metadata;
777
    }
778
779
    /**
780
     * Sets the metadata of the given type for the given table.
781
     * @param string $name table name.
782
     * @param string $type metadata type.
783
     * @param mixed $data metadata.
784
     * @since 2.0.13
785
     */
786
    protected function setTableMetadata($name, $type, $data)
787
    {
788
        $this->_tableMetadata[$this->getRawTableName($name)][$type] = $data;
789
    }
790
791
    /**
792
     * Changes row's array key case to lower if PDO's one is set to uppercase.
793
     * @param array $row row's array or an array of row's arrays.
794
     * @param bool $multiple whether multiple rows or a single row passed.
795
     * @return array normalized row or rows.
796
     * @since 2.0.13
797
     */
798
    protected function normalizePdoRowKeyCase(array $row, $multiple)
799
    {
800
        if ($this->db->getSlavePdo()->getAttribute(\PDO::ATTR_CASE) !== \PDO::CASE_UPPER) {
801
            return $row;
802
        }
803
804
        if ($multiple) {
805
            return array_map(function (array $row) {
806
                return array_change_key_case($row, CASE_LOWER);
807
            }, $row);
808
        }
809
810
        return array_change_key_case($row, CASE_LOWER);
811
    }
812
813
    /**
814
     * Tries to load and populate table metadata from cache.
815
     * @param Cache|null $cache
816
     * @param string $name
817
     */
818
    private function loadTableMetadataFromCache($cache, $name)
819
    {
820
        if ($cache === null) {
821
            $this->_tableMetadata[$name] = [];
822
            return;
823
        }
824
825
        $metadata = $cache->get($this->getCacheKey($name));
826
        if (!is_array($metadata) || !isset($metadata['cacheVersion']) || $metadata['cacheVersion'] !== static::SCHEMA_CACHE_VERSION) {
827
            $this->_tableMetadata[$name] = [];
828
            return;
829
        }
830
831
        unset($metadata['cacheVersion']);
832
        $this->_tableMetadata[$name] = $metadata;
833
    }
834
835
    /**
836
     * Saves table metadata to cache.
837
     * @param Cache|null $cache
838
     * @param string $name
839
     */
840
    private function saveTableMetadataToCache($cache, $name)
841
    {
842
        if ($cache === null) {
843
            return;
844
        }
845
846
        $metadata = $this->_tableMetadata[$name];
847
        $metadata['cacheVersion'] = static::SCHEMA_CACHE_VERSION;
848
        $cache->set(
849
            $this->getCacheKey($name),
850
            $metadata,
851
            $this->db->schemaCacheDuration,
852
            new TagDependency(['tags' => $this->getCacheTag()])
853
        );
854
    }
855
}
856