Passed
Push — scrutinizer-migrate-to-new-eng... ( 58afd6 )
by Alexander
18:11
created

Connection::open()   B

Complexity

Conditions 9
Paths 24

Size

Total Lines 40
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 9.0058

Importance

Changes 0
Metric Value
cc 9
eloc 24
nc 24
nop 0
dl 0
loc 40
ccs 23
cts 24
cp 0.9583
crap 9.0058
rs 8.0555
c 0
b 0
f 0
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 PDO;
11
use Yii;
12
use yii\base\Component;
13
use yii\base\InvalidConfigException;
14
use yii\base\NotSupportedException;
15
use yii\caching\CacheInterface;
16
17
/**
18
 * Connection represents a connection to a database via [PDO](http://php.net/manual/en/book.pdo.php).
19
 *
20
 * Connection works together with [[Command]], [[DataReader]] and [[Transaction]]
21
 * to provide data access to various DBMS in a common set of APIs. They are a thin wrapper
22
 * of the [PDO PHP extension](http://php.net/manual/en/book.pdo.php).
23
 *
24
 * Connection supports database replication and read-write splitting. In particular, a Connection component
25
 * can be configured with multiple [[masters]] and [[slaves]]. It will do load balancing and failover by choosing
26
 * appropriate servers. It will also automatically direct read operations to the slaves and write operations to
27
 * the masters.
28
 *
29
 * To establish a DB connection, set [[dsn]], [[username]] and [[password]], and then
30
 * call [[open()]] to connect to the database server. The current state of the connection can be checked using [[$isActive]].
31
 *
32
 * The following example shows how to create a Connection instance and establish
33
 * the DB connection:
34
 *
35
 * ```php
36
 * $connection = new \yii\db\Connection([
37
 *     'dsn' => $dsn,
38
 *     'username' => $username,
39
 *     'password' => $password,
40
 * ]);
41
 * $connection->open();
42
 * ```
43
 *
44
 * After the DB connection is established, one can execute SQL statements like the following:
45
 *
46
 * ```php
47
 * $command = $connection->createCommand('SELECT * FROM post');
48
 * $posts = $command->queryAll();
49
 * $command = $connection->createCommand('UPDATE post SET status=1');
50
 * $command->execute();
51
 * ```
52
 *
53
 * One can also do prepared SQL execution and bind parameters to the prepared SQL.
54
 * When the parameters are coming from user input, you should use this approach
55
 * to prevent SQL injection attacks. The following is an example:
56
 *
57
 * ```php
58
 * $command = $connection->createCommand('SELECT * FROM post WHERE id=:id');
59
 * $command->bindValue(':id', $_GET['id']);
60
 * $post = $command->query();
61
 * ```
62
 *
63
 * For more information about how to perform various DB queries, please refer to [[Command]].
64
 *
65
 * If the underlying DBMS supports transactions, you can perform transactional SQL queries
66
 * like the following:
67
 *
68
 * ```php
69
 * $transaction = $connection->beginTransaction();
70
 * try {
71
 *     $connection->createCommand($sql1)->execute();
72
 *     $connection->createCommand($sql2)->execute();
73
 *     // ... executing other SQL statements ...
74
 *     $transaction->commit();
75
 * } catch (Exception $e) {
76
 *     $transaction->rollBack();
77
 * }
78
 * ```
79
 *
80
 * You also can use shortcut for the above like the following:
81
 *
82
 * ```php
83
 * $connection->transaction(function () {
84
 *     $order = new Order($customer);
85
 *     $order->save();
86
 *     $order->addItems($items);
87
 * });
88
 * ```
89
 *
90
 * If needed you can pass transaction isolation level as a second parameter:
91
 *
92
 * ```php
93
 * $connection->transaction(function (Connection $db) {
94
 *     //return $db->...
95
 * }, Transaction::READ_UNCOMMITTED);
96
 * ```
97
 *
98
 * Connection is often used as an application component and configured in the application
99
 * configuration like the following:
100
 *
101
 * ```php
102
 * 'components' => [
103
 *     'db' => [
104
 *         'class' => '\yii\db\Connection',
105
 *         'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
106
 *         'username' => 'root',
107
 *         'password' => '',
108
 *         'charset' => 'utf8',
109
 *     ],
110
 * ],
111
 * ```
112
 *
113
 * @property string $driverName Name of the DB driver.
114
 * @property bool $isActive Whether the DB connection is established. This property is read-only.
115
 * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
116
 * sequence object. This property is read-only.
117
 * @property Connection $master The currently active master connection. `null` is returned if there is no
118
 * master available. This property is read-only.
119
 * @property PDO $masterPdo The PDO instance for the currently active master connection. This property is
120
 * read-only.
121
 * @property QueryBuilder $queryBuilder The query builder for the current DB connection. Note that the type of
122
 * this property differs in getter and setter. See [[getQueryBuilder()]] and [[setQueryBuilder()]] for details.
123
 * @property Schema $schema The schema information for the database opened by this connection. This property
124
 * is read-only.
125
 * @property string $serverVersion Server version as a string. This property is read-only.
126
 * @property Connection $slave The currently active slave connection. `null` is returned if there is no slave
127
 * available and `$fallbackToMaster` is false. This property is read-only.
128
 * @property PDO $slavePdo The PDO instance for the currently active slave connection. `null` is returned if
129
 * no slave connection is available and `$fallbackToMaster` is false. This property is read-only.
130
 * @property Transaction|null $transaction The currently active transaction. Null if no active transaction.
131
 * This property is read-only.
132
 *
133
 * @author Qiang Xue <[email protected]>
134
 * @since 2.0
135
 */
136
class Connection extends Component
137
{
138
    /**
139
     * @event yii\base\Event an event that is triggered after a DB connection is established
140
     */
141
    const EVENT_AFTER_OPEN = 'afterOpen';
142
    /**
143
     * @event yii\base\Event an event that is triggered right before a top-level transaction is started
144
     */
145
    const EVENT_BEGIN_TRANSACTION = 'beginTransaction';
146
    /**
147
     * @event yii\base\Event an event that is triggered right after a top-level transaction is committed
148
     */
149
    const EVENT_COMMIT_TRANSACTION = 'commitTransaction';
150
    /**
151
     * @event yii\base\Event an event that is triggered right after a top-level transaction is rolled back
152
     */
153
    const EVENT_ROLLBACK_TRANSACTION = 'rollbackTransaction';
154
155
    /**
156
     * @var string the Data Source Name, or DSN, contains the information required to connect to the database.
157
     * Please refer to the [PHP manual](http://php.net/manual/en/pdo.construct.php) on
158
     * the format of the DSN string.
159
     *
160
     * For [SQLite](http://php.net/manual/en/ref.pdo-sqlite.connection.php) you may use a [path alias](guide:concept-aliases)
161
     * for specifying the database path, e.g. `sqlite:@app/data/db.sql`.
162
     *
163
     * @see charset
164
     */
165
    public $dsn;
166
    /**
167
     * @var string the username for establishing DB connection. Defaults to `null` meaning no username to use.
168
     */
169
    public $username;
170
    /**
171
     * @var string the password for establishing DB connection. Defaults to `null` meaning no password to use.
172
     */
173
    public $password;
174
    /**
175
     * @var array PDO attributes (name => value) that should be set when calling [[open()]]
176
     * to establish a DB connection. Please refer to the
177
     * [PHP manual](http://php.net/manual/en/pdo.setattribute.php) for
178
     * details about available attributes.
179
     */
180
    public $attributes;
181
    /**
182
     * @var PDO the PHP PDO instance associated with this DB connection.
183
     * This property is mainly managed by [[open()]] and [[close()]] methods.
184
     * When a DB connection is active, this property will represent a PDO instance;
185
     * otherwise, it will be null.
186
     * @see pdoClass
187
     */
188
    public $pdo;
189
    /**
190
     * @var bool whether to enable schema caching.
191
     * Note that in order to enable truly schema caching, a valid cache component as specified
192
     * by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true.
193
     * @see schemaCacheDuration
194
     * @see schemaCacheExclude
195
     * @see schemaCache
196
     */
197
    public $enableSchemaCache = false;
198
    /**
199
     * @var int number of seconds that table metadata can remain valid in cache.
200
     * Use 0 to indicate that the cached data will never expire.
201
     * @see enableSchemaCache
202
     */
203
    public $schemaCacheDuration = 3600;
204
    /**
205
     * @var array list of tables whose metadata should NOT be cached. Defaults to empty array.
206
     * The table names may contain schema prefix, if any. Do not quote the table names.
207
     * @see enableSchemaCache
208
     */
209
    public $schemaCacheExclude = [];
210
    /**
211
     * @var CacheInterface|string the cache object or the ID of the cache application component that
212
     * is used to cache the table metadata.
213
     * @see enableSchemaCache
214
     */
215
    public $schemaCache = 'cache';
216
    /**
217
     * @var bool whether to enable query caching.
218
     * Note that in order to enable query caching, a valid cache component as specified
219
     * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
220
     * Also, only the results of the queries enclosed within [[cache()]] will be cached.
221
     * @see queryCache
222
     * @see cache()
223
     * @see noCache()
224
     */
225
    public $enableQueryCache = true;
226
    /**
227
     * @var int the default number of seconds that query results can remain valid in cache.
228
     * Defaults to 3600, meaning 3600 seconds, or one hour. Use 0 to indicate that the cached data will never expire.
229
     * The value of this property will be used when [[cache()]] is called without a cache duration.
230
     * @see enableQueryCache
231
     * @see cache()
232
     */
233
    public $queryCacheDuration = 3600;
234
    /**
235
     * @var CacheInterface|string the cache object or the ID of the cache application component
236
     * that is used for query caching.
237
     * @see enableQueryCache
238
     */
239
    public $queryCache = 'cache';
240
    /**
241
     * @var string the charset used for database connection. The property is only used
242
     * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset
243
     * as configured by the database.
244
     *
245
     * For Oracle Database, the charset must be specified in the [[dsn]], for example for UTF-8 by appending `;charset=UTF-8`
246
     * to the DSN string.
247
     *
248
     * The same applies for if you're using GBK or BIG5 charset with MySQL, then it's highly recommended to
249
     * specify charset via [[dsn]] like `'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'`.
250
     */
251
    public $charset;
252
    /**
253
     * @var bool whether to turn on prepare emulation. Defaults to false, meaning PDO
254
     * will use the native prepare support if available. For some databases (such as MySQL),
255
     * this may need to be set true so that PDO can emulate the prepare support to bypass
256
     * the buggy native prepare support.
257
     * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed.
258
     */
259
    public $emulatePrepare;
260
    /**
261
     * @var string the common prefix or suffix for table names. If a table name is given
262
     * as `{{%TableName}}`, then the percentage character `%` will be replaced with this
263
     * property value. For example, `{{%post}}` becomes `{{tbl_post}}`.
264
     */
265
    public $tablePrefix = '';
266
    /**
267
     * @var array mapping between PDO driver names and [[Schema]] classes.
268
     * The keys of the array are PDO driver names while the values are either the corresponding
269
     * schema class names or configurations. Please refer to [[Yii::createObject()]] for
270
     * details on how to specify a configuration.
271
     *
272
     * This property is mainly used by [[getSchema()]] when fetching the database schema information.
273
     * You normally do not need to set this property unless you want to use your own
274
     * [[Schema]] class to support DBMS that is not supported by Yii.
275
     */
276
    public $schemaMap = [
277
        'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL
278
        'mysqli' => 'yii\db\mysql\Schema', // MySQL
279
        'mysql' => 'yii\db\mysql\Schema', // MySQL
280
        'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
281
        'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
282
        'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts
283
        'oci' => 'yii\db\oci\Schema', // Oracle driver
284
        'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts
285
        'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts
286
        'cubrid' => 'yii\db\cubrid\Schema', // CUBRID
287
    ];
288
    /**
289
     * @var string Custom PDO wrapper class. If not set, it will use [[PDO]] or [[\yii\db\mssql\PDO]] when MSSQL is used.
290
     * @see pdo
291
     */
292
    public $pdoClass;
293
    /**
294
     * @var string the class used to create new database [[Command]] objects. If you want to extend the [[Command]] class,
295
     * you may configure this property to use your extended version of the class.
296
     * Since version 2.0.14 [[$commandMap]] is used if this property is set to its default value.
297
     * @see createCommand
298
     * @since 2.0.7
299
     * @deprecated since 2.0.14. Use [[$commandMap]] for precise configuration.
300
     */
301
    public $commandClass = 'yii\db\Command';
302
    /**
303
     * @var array mapping between PDO driver names and [[Command]] classes.
304
     * The keys of the array are PDO driver names while the values are either the corresponding
305
     * command class names or configurations. Please refer to [[Yii::createObject()]] for
306
     * details on how to specify a configuration.
307
     *
308
     * This property is mainly used by [[createCommand()]] to create new database [[Command]] objects.
309
     * You normally do not need to set this property unless you want to use your own
310
     * [[Command]] class or support DBMS that is not supported by Yii.
311
     * @since 2.0.14
312
     */
313
    public $commandMap = [
314
        'pgsql' => 'yii\db\Command', // PostgreSQL
315
        'mysqli' => 'yii\db\Command', // MySQL
316
        'mysql' => 'yii\db\Command', // MySQL
317
        'sqlite' => 'yii\db\sqlite\Command', // sqlite 3
318
        'sqlite2' => 'yii\db\sqlite\Command', // sqlite 2
319
        'sqlsrv' => 'yii\db\Command', // newer MSSQL driver on MS Windows hosts
320
        'oci' => 'yii\db\Command', // Oracle driver
321
        'mssql' => 'yii\db\Command', // older MSSQL driver on MS Windows hosts
322
        'dblib' => 'yii\db\Command', // dblib drivers on GNU/Linux (and maybe other OSes) hosts
323
        'cubrid' => 'yii\db\Command', // CUBRID
324
    ];
325
    /**
326
     * @var bool whether to enable [savepoint](http://en.wikipedia.org/wiki/Savepoint).
327
     * Note that if the underlying DBMS does not support savepoint, setting this property to be true will have no effect.
328
     */
329
    public $enableSavepoint = true;
330
    /**
331
     * @var CacheInterface|string|false the cache object or the ID of the cache application component that is used to store
332
     * the health status of the DB servers specified in [[masters]] and [[slaves]].
333
     * This is used only when read/write splitting is enabled or [[masters]] is not empty.
334
     * Set boolean `false` to disabled server status caching.
335
     */
336
    public $serverStatusCache = 'cache';
337
    /**
338
     * @var int the retry interval in seconds for dead servers listed in [[masters]] and [[slaves]].
339
     * This is used together with [[serverStatusCache]].
340
     */
341
    public $serverRetryInterval = 600;
342
    /**
343
     * @var bool whether to enable read/write splitting by using [[slaves]] to read data.
344
     * Note that if [[slaves]] is empty, read/write splitting will NOT be enabled no matter what value this property takes.
345
     */
346
    public $enableSlaves = true;
347
    /**
348
     * @var array list of slave connection configurations. Each configuration is used to create a slave DB connection.
349
     * When [[enableSlaves]] is true, one of these configurations will be chosen and used to create a DB connection
350
     * for performing read queries only.
351
     * @see enableSlaves
352
     * @see slaveConfig
353
     */
354
    public $slaves = [];
355
    /**
356
     * @var array the configuration that should be merged with every slave configuration listed in [[slaves]].
357
     * For example,
358
     *
359
     * ```php
360
     * [
361
     *     'username' => 'slave',
362
     *     'password' => 'slave',
363
     *     'attributes' => [
364
     *         // use a smaller connection timeout
365
     *         PDO::ATTR_TIMEOUT => 10,
366
     *     ],
367
     * ]
368
     * ```
369
     */
370
    public $slaveConfig = [];
371
    /**
372
     * @var array list of master connection configurations. Each configuration is used to create a master DB connection.
373
     * When [[open()]] is called, one of these configurations will be chosen and used to create a DB connection
374
     * which will be used by this object.
375
     * Note that when this property is not empty, the connection setting (e.g. "dsn", "username") of this object will
376
     * be ignored.
377
     * @see masterConfig
378
     * @see shuffleMasters
379
     */
380
    public $masters = [];
381
    /**
382
     * @var array the configuration that should be merged with every master configuration listed in [[masters]].
383
     * For example,
384
     *
385
     * ```php
386
     * [
387
     *     'username' => 'master',
388
     *     'password' => 'master',
389
     *     'attributes' => [
390
     *         // use a smaller connection timeout
391
     *         PDO::ATTR_TIMEOUT => 10,
392
     *     ],
393
     * ]
394
     * ```
395
     */
396
    public $masterConfig = [];
397
    /**
398
     * @var bool whether to shuffle [[masters]] before getting one.
399
     * @since 2.0.11
400
     * @see masters
401
     */
402
    public $shuffleMasters = true;
403
    /**
404
     * @var bool whether to enable logging of database queries. Defaults to true.
405
     * You may want to disable this option in a production environment to gain performance
406
     * if you do not need the information being logged.
407
     * @since 2.0.12
408
     * @see enableProfiling
409
     */
410
    public $enableLogging = true;
411
    /**
412
     * @var bool whether to enable profiling of opening database connection and database queries. Defaults to true.
413
     * You may want to disable this option in a production environment to gain performance
414
     * if you do not need the information being logged.
415
     * @since 2.0.12
416
     * @see enableLogging
417
     */
418
    public $enableProfiling = true;
419
420
    /**
421
     * @var Transaction the currently active transaction
422
     */
423
    private $_transaction;
424
    /**
425
     * @var Schema the database schema
426
     */
427
    private $_schema;
428
    /**
429
     * @var string driver name
430
     */
431
    private $_driverName;
432
    /**
433
     * @var Connection|false the currently active master connection
434
     */
435
    private $_master = false;
436
    /**
437
     * @var Connection|false the currently active slave connection
438
     */
439
    private $_slave = false;
440
    /**
441
     * @var array query cache parameters for the [[cache()]] calls
442
     */
443
    private $_queryCacheInfo = [];
444
445
446
    /**
447
     * Returns a value indicating whether the DB connection is established.
448
     * @return bool whether the DB connection is established
449
     */
450 325
    public function getIsActive()
451
    {
452 325
        return $this->pdo !== null;
453
    }
454
455
    /**
456
     * Uses query cache for the queries performed with the callable.
457
     *
458
     * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache),
459
     * queries performed within the callable will be cached and their results will be fetched from cache if available.
460
     * For example,
461
     *
462
     * ```php
463
     * // The customer will be fetched from cache if available.
464
     * // If not, the query will be made against DB and cached for use next time.
465
     * $customer = $db->cache(function (Connection $db) {
466
     *     return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
467
     * });
468
     * ```
469
     *
470
     * Note that query cache is only meaningful for queries that return results. For queries performed with
471
     * [[Command::execute()]], query cache will not be used.
472
     *
473
     * @param callable $callable a PHP callable that contains DB queries which will make use of query cache.
474
     * The signature of the callable is `function (Connection $db)`.
475
     * @param int $duration the number of seconds that query results can remain valid in the cache. If this is
476
     * not set, the value of [[queryCacheDuration]] will be used instead.
477
     * Use 0 to indicate that the cached data will never expire.
478
     * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
479
     * @return mixed the return result of the callable
480
     * @throws \Exception|\Throwable if there is any exception during query
481
     * @see enableQueryCache
482
     * @see queryCache
483
     * @see noCache()
484
     */
485 6
    public function cache(callable $callable, $duration = null, $dependency = null)
486
    {
487 6
        $this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency];
488
        try {
489 6
            $result = call_user_func($callable, $this);
490 6
            array_pop($this->_queryCacheInfo);
491 6
            return $result;
492
        } catch (\Exception $e) {
493
            array_pop($this->_queryCacheInfo);
494
            throw $e;
495
        } catch (\Throwable $e) {
496
            array_pop($this->_queryCacheInfo);
497
            throw $e;
498
        }
499
    }
500
501
    /**
502
     * Disables query cache temporarily.
503
     *
504
     * Queries performed within the callable will not use query cache at all. For example,
505
     *
506
     * ```php
507
     * $db->cache(function (Connection $db) {
508
     *
509
     *     // ... queries that use query cache ...
510
     *
511
     *     return $db->noCache(function (Connection $db) {
512
     *         // this query will not use query cache
513
     *         return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
514
     *     });
515
     * });
516
     * ```
517
     *
518
     * @param callable $callable a PHP callable that contains DB queries which should not use query cache.
519
     * The signature of the callable is `function (Connection $db)`.
520
     * @return mixed the return result of the callable
521
     * @throws \Exception|\Throwable if there is any exception during query
522
     * @see enableQueryCache
523
     * @see queryCache
524
     * @see cache()
525
     */
526 40
    public function noCache(callable $callable)
527
    {
528 40
        $this->_queryCacheInfo[] = false;
529
        try {
530 40
            $result = call_user_func($callable, $this);
531 40
            array_pop($this->_queryCacheInfo);
532 40
            return $result;
533 4
        } catch (\Exception $e) {
534 4
            array_pop($this->_queryCacheInfo);
535 4
            throw $e;
536
        } catch (\Throwable $e) {
537
            array_pop($this->_queryCacheInfo);
538
            throw $e;
539
        }
540
    }
541
542
    /**
543
     * Returns the current query cache information.
544
     * This method is used internally by [[Command]].
545
     * @param int $duration the preferred caching duration. If null, it will be ignored.
546
     * @param \yii\caching\Dependency $dependency the preferred caching dependency. If null, it will be ignored.
547
     * @return array the current query cache information, or null if query cache is not enabled.
548
     * @internal
549
     */
550 1353
    public function getQueryCacheInfo($duration, $dependency)
551
    {
552 1353
        if (!$this->enableQueryCache) {
553 44
            return null;
554
        }
555
556 1351
        $info = end($this->_queryCacheInfo);
557 1351
        if (is_array($info)) {
558 6
            if ($duration === null) {
0 ignored issues
show
introduced by
The condition $duration === null is always false.
Loading history...
559 6
                $duration = $info[0];
560
            }
561 6
            if ($dependency === null) {
562 6
                $dependency = $info[1];
563
            }
564
        }
565
566 1351
        if ($duration === 0 || $duration > 0) {
567 6
            if (is_string($this->queryCache) && Yii::$app) {
568
                $cache = Yii::$app->get($this->queryCache, false);
569
            } else {
570 6
                $cache = $this->queryCache;
571
            }
572 6
            if ($cache instanceof CacheInterface) {
573 6
                return [$cache, $duration, $dependency];
574
            }
575
        }
576
577 1351
        return null;
578
    }
579
580
    /**
581
     * Establishes a DB connection.
582
     * It does nothing if a DB connection has already been established.
583
     * @throws Exception if connection fails
584
     */
585 1828
    public function open()
586
    {
587 1828
        if ($this->pdo !== null) {
588 1455
            return;
589
        }
590
591 1773
        if (!empty($this->masters)) {
592 9
            $db = $this->getMaster();
593 9
            if ($db !== null) {
594 9
                $this->pdo = $db->pdo;
595 9
                return;
596
            }
597
598 8
            throw new InvalidConfigException('None of the master DB servers is available.');
599
        }
600
601 1773
        if (empty($this->dsn)) {
602
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
603
        }
604
605 1773
        $token = 'Opening DB connection: ' . $this->dsn;
606 1773
        $enableProfiling = $this->enableProfiling;
607
        try {
608 1773
            Yii::info($token, __METHOD__);
609 1773
            if ($enableProfiling) {
610 1773
                Yii::beginProfile($token, __METHOD__);
611
            }
612
613 1773
            $this->pdo = $this->createPdoInstance();
614 1773
            $this->initConnection();
615
616 1773
            if ($enableProfiling) {
617 1773
                Yii::endProfile($token, __METHOD__);
618
            }
619 12
        } catch (\PDOException $e) {
620 12
            if ($enableProfiling) {
621 12
                Yii::endProfile($token, __METHOD__);
622
            }
623
624 12
            throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
625
        }
626 1773
    }
627
628
    /**
629
     * Closes the currently active DB connection.
630
     * It does nothing if the connection is already closed.
631
     */
632 1946
    public function close()
633
    {
634 1946
        if ($this->_master) {
635 8
            if ($this->pdo === $this->_master->pdo) {
636 8
                $this->pdo = null;
637
            }
638
639 8
            $this->_master->close();
640 8
            $this->_master = false;
641
        }
642
643 1946
        if ($this->pdo !== null) {
644 1656
            Yii::debug('Closing DB connection: ' . $this->dsn, __METHOD__);
645 1656
            $this->pdo = null;
646 1656
            $this->_schema = null;
647 1656
            $this->_transaction = null;
648
        }
649
650 1946
        if ($this->_slave) {
651 4
            $this->_slave->close();
652 4
            $this->_slave = false;
653
        }
654 1946
    }
655
656
    /**
657
     * Creates the PDO instance.
658
     * This method is called by [[open]] to establish a DB connection.
659
     * The default implementation will create a PHP PDO instance.
660
     * You may override this method if the default PDO needs to be adapted for certain DBMS.
661
     * @return PDO the pdo instance
662
     */
663 1773
    protected function createPdoInstance()
664
    {
665 1773
        $pdoClass = $this->pdoClass;
666 1773
        if ($pdoClass === null) {
0 ignored issues
show
introduced by
The condition $pdoClass === null is always false.
Loading history...
667 1773
            $pdoClass = 'PDO';
668 1773
            if ($this->_driverName !== null) {
669 158
                $driver = $this->_driverName;
670 1620
            } elseif (($pos = strpos($this->dsn, ':')) !== false) {
671 1620
                $driver = strtolower(substr($this->dsn, 0, $pos));
672
            }
673 1773
            if (isset($driver)) {
674 1773
                if ($driver === 'mssql' || $driver === 'dblib') {
675
                    $pdoClass = 'yii\db\mssql\PDO';
676 1773
                } elseif ($driver === 'sqlsrv') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $driver does not seem to be defined for all execution paths leading up to this point.
Loading history...
677
                    $pdoClass = 'yii\db\mssql\SqlsrvPDO';
678
                }
679
            }
680
        }
681
682 1773
        $dsn = $this->dsn;
683 1773
        if (strncmp('sqlite:@', $dsn, 8) === 0) {
684 1
            $dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7));
685
        }
686
687 1773
        return new $pdoClass($dsn, $this->username, $this->password, $this->attributes);
688
    }
689
690
    /**
691
     * Initializes the DB connection.
692
     * This method is invoked right after the DB connection is established.
693
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
694
     * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
695
     * It then triggers an [[EVENT_AFTER_OPEN]] event.
696
     */
697 1773
    protected function initConnection()
698
    {
699 1773
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
700 1773
        if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
701
            $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
702
        }
703 1773
        if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'], true)) {
704
            $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
705
        }
706 1773
        $this->trigger(self::EVENT_AFTER_OPEN);
707 1773
    }
708
709
    /**
710
     * Creates a command for execution.
711
     * @param string $sql the SQL statement to be executed
712
     * @param array $params the parameters to be bound to the SQL statement
713
     * @return Command the DB command
714
     */
715 1441
    public function createCommand($sql = null, $params = [])
716
    {
717 1441
        $driver = $this->getDriverName();
718 1441
        $config = ['class' => 'yii\db\Command'];
719 1441
        if ($this->commandClass !== $config['class']) {
0 ignored issues
show
Deprecated Code introduced by
The property yii\db\Connection::$commandClass has been deprecated: since 2.0.14. Use [[$commandMap]] for precise configuration. ( Ignorable by Annotation )

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

719
        if (/** @scrutinizer ignore-deprecated */ $this->commandClass !== $config['class']) {

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
720
            $config['class'] = $this->commandClass;
0 ignored issues
show
Deprecated Code introduced by
The property yii\db\Connection::$commandClass has been deprecated: since 2.0.14. Use [[$commandMap]] for precise configuration. ( Ignorable by Annotation )

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

720
            $config['class'] = /** @scrutinizer ignore-deprecated */ $this->commandClass;

This property 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 property will be removed from the class and what other property to use instead.

Loading history...
721 1441
        } elseif (isset($this->commandMap[$driver])) {
722 1441
            $config = !is_array($this->commandMap[$driver]) ? ['class' => $this->commandMap[$driver]] : $this->commandMap[$driver];
723
        }
724 1441
        $config['db'] = $this;
725 1441
        $config['sql'] = $sql;
726
        /** @var Command $command */
727 1441
        $command = Yii::createObject($config);
728 1441
        return $command->bindValues($params);
729
    }
730
731
    /**
732
     * Returns the currently active transaction.
733
     * @return Transaction|null the currently active transaction. Null if no active transaction.
734
     */
735 1413
    public function getTransaction()
736
    {
737 1413
        return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
738
    }
739
740
    /**
741
     * Starts a transaction.
742
     * @param string|null $isolationLevel The isolation level to use for this transaction.
743
     * See [[Transaction::begin()]] for details.
744
     * @return Transaction the transaction initiated
745
     */
746 39
    public function beginTransaction($isolationLevel = null)
747
    {
748 39
        $this->open();
749
750 39
        if (($transaction = $this->getTransaction()) === null) {
751 39
            $transaction = $this->_transaction = new Transaction(['db' => $this]);
752
        }
753 39
        $transaction->begin($isolationLevel);
754
755 39
        return $transaction;
756
    }
757
758
    /**
759
     * Executes callback provided in a transaction.
760
     *
761
     * @param callable $callback a valid PHP callback that performs the job. Accepts connection instance as parameter.
762
     * @param string|null $isolationLevel The isolation level to use for this transaction.
763
     * See [[Transaction::begin()]] for details.
764
     * @throws \Exception|\Throwable if there is any exception during query. In this case the transaction will be rolled back.
765
     * @return mixed result of callback function
766
     */
767 23
    public function transaction(callable $callback, $isolationLevel = null)
768
    {
769 23
        $transaction = $this->beginTransaction($isolationLevel);
770 23
        $level = $transaction->level;
771
772
        try {
773 23
            $result = call_user_func($callback, $this);
774 15
            if ($transaction->isActive && $transaction->level === $level) {
775 15
                $transaction->commit();
776
            }
777 8
        } catch (\Exception $e) {
778 8
            $this->rollbackTransactionOnLevel($transaction, $level);
779 8
            throw $e;
780
        } catch (\Throwable $e) {
781
            $this->rollbackTransactionOnLevel($transaction, $level);
782
            throw $e;
783
        }
784
785 15
        return $result;
786
    }
787
788
    /**
789
     * Rolls back given [[Transaction]] object if it's still active and level match.
790
     * In some cases rollback can fail, so this method is fail safe. Exception thrown
791
     * from rollback will be caught and just logged with [[\Yii::error()]].
792
     * @param Transaction $transaction Transaction object given from [[beginTransaction()]].
793
     * @param int $level Transaction level just after [[beginTransaction()]] call.
794
     */
795 8
    private function rollbackTransactionOnLevel($transaction, $level)
796
    {
797 8
        if ($transaction->isActive && $transaction->level === $level) {
798
            // https://github.com/yiisoft/yii2/pull/13347
799
            try {
800 8
                $transaction->rollBack();
801
            } catch (\Exception $e) {
802
                \Yii::error($e, __METHOD__);
803
                // hide this exception to be able to continue throwing original exception outside
804
            }
805
        }
806 8
    }
807
808
    /**
809
     * Returns the schema information for the database opened by this connection.
810
     * @return Schema the schema information for the database opened by this connection.
811
     * @throws NotSupportedException if there is no support for the current driver type
812
     */
813 1821
    public function getSchema()
814
    {
815 1821
        if ($this->_schema !== null) {
816 1514
            return $this->_schema;
817
        }
818
819 1766
        $driver = $this->getDriverName();
820 1766
        if (isset($this->schemaMap[$driver])) {
821 1766
            $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver];
822 1766
            $config['db'] = $this;
823
824 1766
            return $this->_schema = Yii::createObject($config);
825
        }
826
827
        throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
828
    }
829
830
    /**
831
     * Returns the query builder for the current DB connection.
832
     * @return QueryBuilder the query builder for the current DB connection.
833
     */
834 1036
    public function getQueryBuilder()
835
    {
836 1036
        return $this->getSchema()->getQueryBuilder();
837
    }
838
839
    /**
840
     * Can be used to set [[QueryBuilder]] configuration via Connection configuration array.
841
     *
842
     * @param array $value the [[QueryBuilder]] properties to be configured.
843
     * @since 2.0.14
844
     */
845
    public function setQueryBuilder($value)
846
    {
847
        Yii::configure($this->getQueryBuilder(), $value);
848
    }
849
850
    /**
851
     * Obtains the schema information for the named table.
852
     * @param string $name table name.
853
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
854
     * @return TableSchema table schema information. Null if the named table does not exist.
855
     */
856 217
    public function getTableSchema($name, $refresh = false)
857
    {
858 217
        return $this->getSchema()->getTableSchema($name, $refresh);
859
    }
860
861
    /**
862
     * Returns the ID of the last inserted row or sequence value.
863
     * @param string $sequenceName name of the sequence object (required by some DBMS)
864
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
865
     * @see http://php.net/manual/en/pdo.lastinsertid.php
866
     */
867
    public function getLastInsertID($sequenceName = '')
868
    {
869
        return $this->getSchema()->getLastInsertID($sequenceName);
870
    }
871
872
    /**
873
     * Quotes a string value for use in a query.
874
     * Note that if the parameter is not a string, it will be returned without change.
875
     * @param string $value string to be quoted
876
     * @return string the properly quoted string
877
     * @see http://php.net/manual/en/pdo.quote.php
878
     */
879 1015
    public function quoteValue($value)
880
    {
881 1015
        return $this->getSchema()->quoteValue($value);
882
    }
883
884
    /**
885
     * Quotes a table name for use in a query.
886
     * If the table name contains schema prefix, the prefix will also be properly quoted.
887
     * If the table name is already quoted or contains special characters including '(', '[[' and '{{',
888
     * then this method will do nothing.
889
     * @param string $name table name
890
     * @return string the properly quoted table name
891
     */
892 1261
    public function quoteTableName($name)
893
    {
894 1261
        return $this->getSchema()->quoteTableName($name);
895
    }
896
897
    /**
898
     * Quotes a column name for use in a query.
899
     * If the column name contains prefix, the prefix will also be properly quoted.
900
     * If the column name is already quoted or contains special characters including '(', '[[' and '{{',
901
     * then this method will do nothing.
902
     * @param string $name column name
903
     * @return string the properly quoted column name
904
     */
905 1269
    public function quoteColumnName($name)
906
    {
907 1269
        return $this->getSchema()->quoteColumnName($name);
908
    }
909
910
    /**
911
     * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
912
     * Tokens enclosed within double curly brackets are treated as table names, while
913
     * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
914
     * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
915
     * with [[tablePrefix]].
916
     * @param string $sql the SQL to be quoted
917
     * @return string the quoted SQL
918
     */
919 1484
    public function quoteSql($sql)
920
    {
921 1484
        return preg_replace_callback(
922 1484
            '/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
923 1484
            function ($matches) {
924 752
                if (isset($matches[3])) {
925 537
                    return $this->quoteColumnName($matches[3]);
926
                }
927
928 671
                return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
929 1484
            },
930 1484
            $sql
931
        );
932
    }
933
934
    /**
935
     * Returns the name of the DB driver. Based on the the current [[dsn]], in case it was not set explicitly
936
     * by an end user.
937
     * @return string name of the DB driver
938
     */
939 1837
    public function getDriverName()
940
    {
941 1837
        if ($this->_driverName === null) {
942 1764
            if (($pos = strpos($this->dsn, ':')) !== false) {
943 1764
                $this->_driverName = strtolower(substr($this->dsn, 0, $pos));
944
            } else {
945
                $this->_driverName = strtolower($this->getSlavePdo()->getAttribute(PDO::ATTR_DRIVER_NAME));
946
            }
947
        }
948
949 1837
        return $this->_driverName;
950
    }
951
952
    /**
953
     * Changes the current driver name.
954
     * @param string $driverName name of the DB driver
955
     */
956
    public function setDriverName($driverName)
957
    {
958
        $this->_driverName = strtolower($driverName);
959
    }
960
961
    /**
962
     * Returns a server version as a string comparable by [[\version_compare()]].
963
     * @return string server version as a string.
964
     * @since 2.0.14
965
     */
966 65
    public function getServerVersion()
967
    {
968 65
        return $this->getSchema()->getServerVersion();
969
    }
970
971
    /**
972
     * Returns the PDO instance for the currently active slave connection.
973
     * When [[enableSlaves]] is true, one of the slaves will be used for read queries, and its PDO instance
974
     * will be returned by this method.
975
     * @param bool $fallbackToMaster whether to return a master PDO in case none of the slave connections is available.
976
     * @return PDO the PDO instance for the currently active slave connection. `null` is returned if no slave connection
977
     * is available and `$fallbackToMaster` is false.
978
     */
979 1561
    public function getSlavePdo($fallbackToMaster = true)
980
    {
981 1561
        $db = $this->getSlave(false);
982 1561
        if ($db === null) {
983 1557
            return $fallbackToMaster ? $this->getMasterPdo() : null;
984
        }
985
986 5
        return $db->pdo;
987
    }
988
989
    /**
990
     * Returns the PDO instance for the currently active master connection.
991
     * This method will open the master DB connection and then return [[pdo]].
992
     * @return PDO the PDO instance for the currently active master connection.
993
     */
994 1591
    public function getMasterPdo()
995
    {
996 1591
        $this->open();
997 1591
        return $this->pdo;
998
    }
999
1000
    /**
1001
     * Returns the currently active slave connection.
1002
     * If this method is called for the first time, it will try to open a slave connection when [[enableSlaves]] is true.
1003
     * @param bool $fallbackToMaster whether to return a master connection in case there is no slave connection available.
1004
     * @return Connection the currently active slave connection. `null` is returned if there is no slave available and
1005
     * `$fallbackToMaster` is false.
1006
     */
1007 1563
    public function getSlave($fallbackToMaster = true)
1008
    {
1009 1563
        if (!$this->enableSlaves) {
1010 202
            return $fallbackToMaster ? $this : null;
1011
        }
1012
1013 1465
        if ($this->_slave === false) {
1014 1465
            $this->_slave = $this->openFromPool($this->slaves, $this->slaveConfig);
1015
        }
1016
1017 1459
        return $this->_slave === null && $fallbackToMaster ? $this : $this->_slave;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_slave ===...? $this : $this->_slave also could return the type true which is incompatible with the documented return type yii\db\Connection.
Loading history...
1018
    }
1019
1020
    /**
1021
     * Returns the currently active master connection.
1022
     * If this method is called for the first time, it will try to open a master connection.
1023
     * @return Connection the currently active master connection. `null` is returned if there is no master available.
1024
     * @since 2.0.11
1025
     */
1026 11
    public function getMaster()
1027
    {
1028 11
        if ($this->_master === false) {
1029 11
            $this->_master = $this->shuffleMasters
1030 2
                ? $this->openFromPool($this->masters, $this->masterConfig)
1031 9
                : $this->openFromPoolSequentially($this->masters, $this->masterConfig);
1032
        }
1033
1034 11
        return $this->_master;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_master also could return the type true which is incompatible with the documented return type yii\db\Connection.
Loading history...
1035
    }
1036
1037
    /**
1038
     * Executes the provided callback by using the master connection.
1039
     *
1040
     * This method is provided so that you can temporarily force using the master connection to perform
1041
     * DB operations even if they are read queries. For example,
1042
     *
1043
     * ```php
1044
     * $result = $db->useMaster(function ($db) {
1045
     *     return $db->createCommand('SELECT * FROM user LIMIT 1')->queryOne();
1046
     * });
1047
     * ```
1048
     *
1049
     * @param callable $callback a PHP callable to be executed by this method. Its signature is
1050
     * `function (Connection $db)`. Its return value will be returned by this method.
1051
     * @return mixed the return value of the callback
1052
     * @throws \Exception|\Throwable if there is any exception thrown from the callback
1053
     */
1054 97
    public function useMaster(callable $callback)
1055
    {
1056 97
        if ($this->enableSlaves) {
1057 97
            $this->enableSlaves = false;
1058
            try {
1059 97
                $result = call_user_func($callback, $this);
1060 4
            } catch (\Exception $e) {
1061 4
                $this->enableSlaves = true;
1062 4
                throw $e;
1063
            } catch (\Throwable $e) {
1064
                $this->enableSlaves = true;
1065
                throw $e;
1066
            }
1067
            // TODO: use "finally" keyword when miminum required PHP version is >= 5.5
1068 93
            $this->enableSlaves = true;
1069
        } else {
1070
            $result = call_user_func($callback, $this);
1071
        }
1072
1073 93
        return $result;
1074
    }
1075
1076
    /**
1077
     * Opens the connection to a server in the pool.
1078
     * This method implements the load balancing among the given list of the servers.
1079
     * Connections will be tried in random order.
1080
     * @param array $pool the list of connection configurations in the server pool
1081
     * @param array $sharedConfig the configuration common to those given in `$pool`.
1082
     * @return Connection the opened DB connection, or `null` if no server is available
1083
     * @throws InvalidConfigException if a configuration does not specify "dsn"
1084
     */
1085 1465
    protected function openFromPool(array $pool, array $sharedConfig)
1086
    {
1087 1465
        shuffle($pool);
1088 1465
        return $this->openFromPoolSequentially($pool, $sharedConfig);
1089
    }
1090
1091
    /**
1092
     * Opens the connection to a server in the pool.
1093
     * This method implements the load balancing among the given list of the servers.
1094
     * Connections will be tried in sequential order.
1095
     * @param array $pool the list of connection configurations in the server pool
1096
     * @param array $sharedConfig the configuration common to those given in `$pool`.
1097
     * @return Connection the opened DB connection, or `null` if no server is available
1098
     * @throws InvalidConfigException if a configuration does not specify "dsn"
1099
     * @since 2.0.11
1100
     */
1101 1473
    protected function openFromPoolSequentially(array $pool, array $sharedConfig)
1102
    {
1103 1473
        if (empty($pool)) {
1104 1453
            return null;
1105
        }
1106
1107 21
        if (!isset($sharedConfig['class'])) {
1108 21
            $sharedConfig['class'] = get_class($this);
1109
        }
1110
1111 21
        $cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache;
1112
1113 21
        foreach ($pool as $config) {
1114 21
            $config = array_merge($sharedConfig, $config);
1115 21
            if (empty($config['dsn'])) {
1116 6
                throw new InvalidConfigException('The "dsn" option must be specified.');
1117
            }
1118
1119 15
            $key = [__METHOD__, $config['dsn']];
1120 15
            if ($cache instanceof CacheInterface && $cache->get($key)) {
1121
                // should not try this dead server now
1122
                continue;
1123
            }
1124
1125
            /* @var $db Connection */
1126 15
            $db = Yii::createObject($config);
1127
1128
            try {
1129 15
                $db->open();
1130 15
                return $db;
1131 8
            } catch (\Exception $e) {
1132 8
                Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
1133 8
                if ($cache instanceof CacheInterface) {
1134
                    // mark this server as dead and only retry it after the specified interval
1135 8
                    $cache->set($key, 1, $this->serverRetryInterval);
1136
                }
1137
            }
1138
        }
1139
1140 8
        return null;
1141
    }
1142
1143
    /**
1144
     * Close the connection before serializing.
1145
     * @return array
1146
     */
1147 18
    public function __sleep()
1148
    {
1149 18
        $fields = (array) $this;
1150
1151 18
        unset($fields['pdo']);
1152 18
        unset($fields["\000" . __CLASS__ . "\000" . '_master']);
1153 18
        unset($fields["\000" . __CLASS__ . "\000" . '_slave']);
1154 18
        unset($fields["\000" . __CLASS__ . "\000" . '_transaction']);
1155 18
        unset($fields["\000" . __CLASS__ . "\000" . '_schema']);
1156
1157 18
        return array_keys($fields);
1158
    }
1159
1160
    /**
1161
     * Reset the connection after cloning.
1162
     */
1163 7
    public function __clone()
1164
    {
1165 7
        parent::__clone();
1166
1167 7
        $this->_master = false;
1168 7
        $this->_slave = false;
1169 7
        $this->_schema = null;
1170 7
        $this->_transaction = null;
1171 7
        if (strncmp($this->dsn, 'sqlite::memory:', 15) !== 0) {
1172
            // reset PDO connection, unless its sqlite in-memory, which can only have one connection
1173 6
            $this->pdo = null;
1174
        }
1175 7
    }
1176
}
1177