Passed
Push — fix-array-access ( 050ccc...ecf3a7 )
by Alexander
56:12 queued 49:24
created

Connection   F

Complexity

Total Complexity 122

Size/Duplication

Total Lines 1115
Duplicated Lines 0 %

Test Coverage

Coverage 84.86%

Importance

Changes 8
Bugs 0 Features 0
Metric Value
eloc 318
dl 0
loc 1115
ccs 241
cts 284
cp 0.8486
rs 2
c 8
b 0
f 0
wmc 122

34 Methods

Rating   Name   Duplication   Size   Complexity  
A getIsActive() 0 3 1
A noCache() 0 13 3
B getQueryCacheInfo() 0 28 10
A cache() 0 13 4
A close() 0 27 5
B open() 0 43 10
B initConnection() 0 15 8
A __clone() 0 11 2
A setQueryBuilder() 0 3 1
A setDriverName() 0 3 1
A quoteValue() 0 3 1
A quoteColumnName() 0 6 2
A getMasterPdo() 0 4 1
A transaction() 0 19 5
C openFromPoolSequentially() 0 64 13
A getQueryBuilder() 0 3 1
A quoteSql() 0 12 2
A getLastInsertID() 0 3 1
A getSlavePdo() 0 8 3
A getSchema() 0 15 4
A getDriverName() 0 11 3
A rollbackTransactionOnLevel() 0 8 4
A quoteTableName() 0 6 2
A getMaster() 0 9 3
A getTableSchema() 0 3 1
A useMaster() 0 20 4
A getSlave() 0 11 6
A getTransaction() 0 3 3
A __sleep() 0 11 1
A beginTransaction() 0 10 2
A getServerVersion() 0 3 1
A openFromPool() 0 4 1
A createCommand() 0 14 4
B createPdoInstance() 0 27 9

How to fix   Complexity   

Complex Class

Complex classes like Connection often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

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

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

1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use 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](https://secure.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](https://secure.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-read bool $isActive Whether the DB connection is established. This property is read-only.
115
 * @property-read string $lastInsertID The row ID of the last row inserted, or the last value retrieved from
116
 * the sequence object. This property is read-only.
117
 * @property-read Connection $master The currently active master connection. `null` is returned if there is no
118
 * master available. This property is read-only.
119
 * @property-read 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-read Schema $schema The schema information for the database opened by this connection. This
124
 * property is read-only.
125
 * @property-read string $serverVersion Server version as a string. This property is read-only.
126
 * @property-read Connection $slave The currently active slave connection. `null` is returned if there is no
127
 * slave available and `$fallbackToMaster` is false. This property is read-only.
128
 * @property-read PDO $slavePdo The PDO instance for the currently active slave connection. `null` is returned
129
 * if no slave connection is available and `$fallbackToMaster` is false. This property is read-only.
130
 * @property-read Transaction|null $transaction The currently active transaction. Null if no active
131
 * transaction. 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](https://secure.php.net/manual/en/pdo.construct.php) on
158
     * the format of the DSN string.
159
     *
160
     * For [SQLite](https://secure.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](https://secure.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\oci\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
     * @see openFromPoolSequentially() for details about the failover behavior.
336
     * @see serverRetryInterval
337
     */
338
    public $serverStatusCache = 'cache';
339
    /**
340
     * @var int the retry interval in seconds for dead servers listed in [[masters]] and [[slaves]].
341
     * This is used together with [[serverStatusCache]].
342
     */
343
    public $serverRetryInterval = 600;
344
    /**
345
     * @var bool whether to enable read/write splitting by using [[slaves]] to read data.
346
     * Note that if [[slaves]] is empty, read/write splitting will NOT be enabled no matter what value this property takes.
347
     */
348
    public $enableSlaves = true;
349
    /**
350
     * @var array list of slave connection configurations. Each configuration is used to create a slave DB connection.
351
     * When [[enableSlaves]] is true, one of these configurations will be chosen and used to create a DB connection
352
     * for performing read queries only.
353
     * @see enableSlaves
354
     * @see slaveConfig
355
     */
356
    public $slaves = [];
357
    /**
358
     * @var array the configuration that should be merged with every slave configuration listed in [[slaves]].
359
     * For example,
360
     *
361
     * ```php
362
     * [
363
     *     'username' => 'slave',
364
     *     'password' => 'slave',
365
     *     'attributes' => [
366
     *         // use a smaller connection timeout
367
     *         PDO::ATTR_TIMEOUT => 10,
368
     *     ],
369
     * ]
370
     * ```
371
     */
372
    public $slaveConfig = [];
373
    /**
374
     * @var array list of master connection configurations. Each configuration is used to create a master DB connection.
375
     * When [[open()]] is called, one of these configurations will be chosen and used to create a DB connection
376
     * which will be used by this object.
377
     * Note that when this property is not empty, the connection setting (e.g. "dsn", "username") of this object will
378
     * be ignored.
379
     * @see masterConfig
380
     * @see shuffleMasters
381
     */
382
    public $masters = [];
383
    /**
384
     * @var array the configuration that should be merged with every master configuration listed in [[masters]].
385
     * For example,
386
     *
387
     * ```php
388
     * [
389
     *     'username' => 'master',
390
     *     'password' => 'master',
391
     *     'attributes' => [
392
     *         // use a smaller connection timeout
393
     *         PDO::ATTR_TIMEOUT => 10,
394
     *     ],
395
     * ]
396
     * ```
397
     */
398
    public $masterConfig = [];
399
    /**
400
     * @var bool whether to shuffle [[masters]] before getting one.
401
     * @since 2.0.11
402
     * @see masters
403
     */
404
    public $shuffleMasters = true;
405
    /**
406
     * @var bool whether to enable logging of database queries. Defaults to true.
407
     * You may want to disable this option in a production environment to gain performance
408
     * if you do not need the information being logged.
409
     * @since 2.0.12
410
     * @see enableProfiling
411
     */
412
    public $enableLogging = true;
413
    /**
414
     * @var bool whether to enable profiling of opening database connection and database queries. Defaults to true.
415
     * You may want to disable this option in a production environment to gain performance
416
     * if you do not need the information being logged.
417
     * @since 2.0.12
418
     * @see enableLogging
419
     */
420
    public $enableProfiling = true;
421
    /**
422
     * @var bool If the database connected via pdo_dblib is SyBase.
423
     * @since 2.0.38
424
     */
425
    public $isSybase = false;
426
427
    /**
428
     * @var Transaction the currently active transaction
429
     */
430
    private $_transaction;
431
    /**
432
     * @var Schema the database schema
433
     */
434
    private $_schema;
435
    /**
436
     * @var string driver name
437
     */
438
    private $_driverName;
439
    /**
440
     * @var Connection|false the currently active master connection
441
     */
442
    private $_master = false;
443
    /**
444
     * @var Connection|false the currently active slave connection
445
     */
446
    private $_slave = false;
447
    /**
448
     * @var array query cache parameters for the [[cache()]] calls
449
     */
450
    private $_queryCacheInfo = [];
451
    /**
452
     * @var string[] quoted table name cache for [[quoteTableName()]] calls
453
     */
454
    private $_quotedTableNames;
455
    /**
456
     * @var string[] quoted column name cache for [[quoteColumnName()]] calls
457
     */
458
    private $_quotedColumnNames;
459
460
461
    /**
462
     * Returns a value indicating whether the DB connection is established.
463
     * @return bool whether the DB connection is established
464
     */
465 334
    public function getIsActive()
466
    {
467 334
        return $this->pdo !== null;
468
    }
469
470
    /**
471
     * Uses query cache for the queries performed with the callable.
472
     *
473
     * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache),
474
     * queries performed within the callable will be cached and their results will be fetched from cache if available.
475
     * For example,
476
     *
477
     * ```php
478
     * // The customer will be fetched from cache if available.
479
     * // If not, the query will be made against DB and cached for use next time.
480
     * $customer = $db->cache(function (Connection $db) {
481
     *     return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
482
     * });
483
     * ```
484
     *
485
     * Note that query cache is only meaningful for queries that return results. For queries performed with
486
     * [[Command::execute()]], query cache will not be used.
487
     *
488
     * @param callable $callable a PHP callable that contains DB queries which will make use of query cache.
489
     * The signature of the callable is `function (Connection $db)`.
490
     * @param int $duration the number of seconds that query results can remain valid in the cache. If this is
491
     * not set, the value of [[queryCacheDuration]] will be used instead.
492
     * Use 0 to indicate that the cached data will never expire.
493
     * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
494
     * @return mixed the return result of the callable
495
     * @throws \Exception|\Throwable if there is any exception during query
496
     * @see enableQueryCache
497
     * @see queryCache
498
     * @see noCache()
499
     */
500 6
    public function cache(callable $callable, $duration = null, $dependency = null)
501
    {
502 6
        $this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency];
503
        try {
504 6
            $result = call_user_func($callable, $this);
505 6
            array_pop($this->_queryCacheInfo);
506 6
            return $result;
507
        } catch (\Exception $e) {
508
            array_pop($this->_queryCacheInfo);
509
            throw $e;
510
        } catch (\Throwable $e) {
511
            array_pop($this->_queryCacheInfo);
512
            throw $e;
513
        }
514
    }
515
516
    /**
517
     * Disables query cache temporarily.
518
     *
519
     * Queries performed within the callable will not use query cache at all. For example,
520
     *
521
     * ```php
522
     * $db->cache(function (Connection $db) {
523
     *
524
     *     // ... queries that use query cache ...
525
     *
526
     *     return $db->noCache(function (Connection $db) {
527
     *         // this query will not use query cache
528
     *         return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
529
     *     });
530
     * });
531
     * ```
532
     *
533
     * @param callable $callable a PHP callable that contains DB queries which should not use query cache.
534
     * The signature of the callable is `function (Connection $db)`.
535
     * @return mixed the return result of the callable
536
     * @throws \Exception|\Throwable if there is any exception during query
537
     * @see enableQueryCache
538
     * @see queryCache
539
     * @see cache()
540
     */
541 40
    public function noCache(callable $callable)
542
    {
543 40
        $this->_queryCacheInfo[] = false;
544
        try {
545 40
            $result = call_user_func($callable, $this);
546 40
            array_pop($this->_queryCacheInfo);
547 40
            return $result;
548 4
        } catch (\Exception $e) {
549 4
            array_pop($this->_queryCacheInfo);
550 4
            throw $e;
551
        } catch (\Throwable $e) {
552
            array_pop($this->_queryCacheInfo);
553
            throw $e;
554
        }
555
    }
556
557
    /**
558
     * Returns the current query cache information.
559
     * This method is used internally by [[Command]].
560
     * @param int $duration the preferred caching duration. If null, it will be ignored.
561
     * @param \yii\caching\Dependency $dependency the preferred caching dependency. If null, it will be ignored.
562
     * @return array the current query cache information, or null if query cache is not enabled.
563
     * @internal
564
     */
565 1524
    public function getQueryCacheInfo($duration, $dependency)
566
    {
567 1524
        if (!$this->enableQueryCache) {
568 44
            return null;
569
        }
570
571 1522
        $info = end($this->_queryCacheInfo);
572 1522
        if (is_array($info)) {
573 6
            if ($duration === null) {
0 ignored issues
show
introduced by
The condition $duration === null is always false.
Loading history...
574 6
                $duration = $info[0];
575
            }
576 6
            if ($dependency === null) {
577 6
                $dependency = $info[1];
578
            }
579
        }
580
581 1522
        if ($duration === 0 || $duration > 0) {
582 6
            if (is_string($this->queryCache) && Yii::$app) {
583
                $cache = Yii::$app->get($this->queryCache, false);
584
            } else {
585 6
                $cache = $this->queryCache;
586
            }
587 6
            if ($cache instanceof CacheInterface) {
588 6
                return [$cache, $duration, $dependency];
589
            }
590
        }
591
592 1522
        return null;
593
    }
594
595
    /**
596
     * Establishes a DB connection.
597
     * It does nothing if a DB connection has already been established.
598
     * @throws Exception if connection fails
599
     */
600 2057
    public function open()
601
    {
602 2057
        if ($this->pdo !== null) {
603 1636
            return;
604
        }
605
606 2002
        if (!empty($this->masters)) {
607 9
            $db = $this->getMaster();
608 9
            if ($db !== null) {
609 9
                $this->pdo = $db->pdo;
610 9
                return;
611
            }
612
613 8
            throw new InvalidConfigException('None of the master DB servers is available.');
614
        }
615
616 2002
        if (empty($this->dsn)) {
617
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
618
        }
619
620 2002
        $token = 'Opening DB connection: ' . $this->dsn;
621 2002
        $enableProfiling = $this->enableProfiling;
622
        try {
623 2002
            if ($this->enableLogging) {
624 2002
                Yii::info($token, __METHOD__);
625
            }
626
627 2002
            if ($enableProfiling) {
628 2002
                Yii::beginProfile($token, __METHOD__);
629
            }
630
631 2002
            $this->pdo = $this->createPdoInstance();
632 2002
            $this->initConnection();
633
634 2002
            if ($enableProfiling) {
635 2002
                Yii::endProfile($token, __METHOD__);
636
            }
637 12
        } catch (\PDOException $e) {
638 12
            if ($enableProfiling) {
639 12
                Yii::endProfile($token, __METHOD__);
640
            }
641
642 12
            throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
643
        }
644 2002
    }
645
646
    /**
647
     * Closes the currently active DB connection.
648
     * It does nothing if the connection is already closed.
649
     */
650 2148
    public function close()
651
    {
652 2148
        if ($this->_master) {
653 8
            if ($this->pdo === $this->_master->pdo) {
654 8
                $this->pdo = null;
655
            }
656
657 8
            $this->_master->close();
658 8
            $this->_master = false;
659
        }
660
661 2148
        if ($this->pdo !== null) {
662 1799
            Yii::debug('Closing DB connection: ' . $this->dsn, __METHOD__);
663 1799
            $this->pdo = null;
664
        }
665
666 2148
        if ($this->_slave) {
667 4
            $this->_slave->close();
668 4
            $this->_slave = false;
669
        }
670
671 2148
        $this->_schema = null;
672 2148
        $this->_transaction = null;
673 2148
        $this->_driverName = null;
674 2148
        $this->_queryCacheInfo = [];
675 2148
        $this->_quotedTableNames = null;
676 2148
        $this->_quotedColumnNames = null;
677 2148
    }
678
679
    /**
680
     * Creates the PDO instance.
681
     * This method is called by [[open]] to establish a DB connection.
682
     * The default implementation will create a PHP PDO instance.
683
     * You may override this method if the default PDO needs to be adapted for certain DBMS.
684
     * @return PDO the pdo instance
685
     */
686 2002
    protected function createPdoInstance()
687
    {
688 2002
        $pdoClass = $this->pdoClass;
689 2002
        if ($pdoClass === null) {
0 ignored issues
show
introduced by
The condition $pdoClass === null is always false.
Loading history...
690 2002
            $pdoClass = 'PDO';
691 2002
            if ($this->_driverName !== null) {
692 247
                $driver = $this->_driverName;
693 1760
            } elseif (($pos = strpos($this->dsn, ':')) !== false) {
694 1760
                $driver = strtolower(substr($this->dsn, 0, $pos));
695
            }
696 2002
            if (isset($driver)) {
697 2002
                if ($driver === 'mssql') {
698
                    $pdoClass = 'yii\db\mssql\PDO';
699 2002
                } elseif ($driver === 'dblib') {
700
                    $pdoClass = 'yii\db\mssql\DBLibPDO';
701 2002
                } 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...
702
                    $pdoClass = 'yii\db\mssql\SqlsrvPDO';
703
                }
704
            }
705
        }
706
707 2002
        $dsn = $this->dsn;
708 2002
        if (strncmp('sqlite:@', $dsn, 8) === 0) {
709 1
            $dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7));
710
        }
711
712 2002
        return new $pdoClass($dsn, $this->username, $this->password, $this->attributes);
713
    }
714
715
    /**
716
     * Initializes the DB connection.
717
     * This method is invoked right after the DB connection is established.
718
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
719
     * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
720
     * It then triggers an [[EVENT_AFTER_OPEN]] event.
721
     */
722 2002
    protected function initConnection()
723
    {
724 2002
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
725 2002
        if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
726
            if ($this->driverName !== 'sqlsrv') {
727
                $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
728
            }
729
        }
730 2002
        if (!$this->isSybase && in_array($this->getDriverName(), ['mssql', 'dblib'], true)) {
731
            $this->pdo->exec('SET ANSI_NULL_DFLT_ON ON');
732
        }
733 2002
        if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'], true)) {
734
            $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
735
        }
736 2002
        $this->trigger(self::EVENT_AFTER_OPEN);
737 2002
    }
738
739
    /**
740
     * Creates a command for execution.
741
     * @param string $sql the SQL statement to be executed
742
     * @param array $params the parameters to be bound to the SQL statement
743
     * @return Command the DB command
744
     */
745 1618
    public function createCommand($sql = null, $params = [])
746
    {
747 1618
        $driver = $this->getDriverName();
748 1618
        $config = ['class' => 'yii\db\Command'];
749 1618
        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

749
        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...
750
            $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

750
            $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...
751 1618
        } elseif (isset($this->commandMap[$driver])) {
752 1618
            $config = !is_array($this->commandMap[$driver]) ? ['class' => $this->commandMap[$driver]] : $this->commandMap[$driver];
753
        }
754 1618
        $config['db'] = $this;
755 1618
        $config['sql'] = $sql;
756
        /** @var Command $command */
757 1618
        $command = Yii::createObject($config);
758 1618
        return $command->bindValues($params);
759
    }
760
761
    /**
762
     * Returns the currently active transaction.
763
     * @return Transaction|null the currently active transaction. Null if no active transaction.
764
     */
765 1587
    public function getTransaction()
766
    {
767 1587
        return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
768
    }
769
770
    /**
771
     * Starts a transaction.
772
     * @param string|null $isolationLevel The isolation level to use for this transaction.
773
     * See [[Transaction::begin()]] for details.
774
     * @return Transaction the transaction initiated
775
     */
776 41
    public function beginTransaction($isolationLevel = null)
777
    {
778 41
        $this->open();
779
780 41
        if (($transaction = $this->getTransaction()) === null) {
781 41
            $transaction = $this->_transaction = new Transaction(['db' => $this]);
782
        }
783 41
        $transaction->begin($isolationLevel);
784
785 41
        return $transaction;
786
    }
787
788
    /**
789
     * Executes callback provided in a transaction.
790
     *
791
     * @param callable $callback a valid PHP callback that performs the job. Accepts connection instance as parameter.
792
     * @param string|null $isolationLevel The isolation level to use for this transaction.
793
     * See [[Transaction::begin()]] for details.
794
     * @throws \Exception|\Throwable if there is any exception during query. In this case the transaction will be rolled back.
795
     * @return mixed result of callback function
796
     */
797 25
    public function transaction(callable $callback, $isolationLevel = null)
798
    {
799 25
        $transaction = $this->beginTransaction($isolationLevel);
800 25
        $level = $transaction->level;
801
802
        try {
803 25
            $result = call_user_func($callback, $this);
804 17
            if ($transaction->isActive && $transaction->level === $level) {
805 17
                $transaction->commit();
806
            }
807 8
        } catch (\Exception $e) {
808 8
            $this->rollbackTransactionOnLevel($transaction, $level);
809 8
            throw $e;
810
        } catch (\Throwable $e) {
811
            $this->rollbackTransactionOnLevel($transaction, $level);
812
            throw $e;
813
        }
814
815 17
        return $result;
816
    }
817
818
    /**
819
     * Rolls back given [[Transaction]] object if it's still active and level match.
820
     * In some cases rollback can fail, so this method is fail safe. Exception thrown
821
     * from rollback will be caught and just logged with [[\Yii::error()]].
822
     * @param Transaction $transaction Transaction object given from [[beginTransaction()]].
823
     * @param int $level Transaction level just after [[beginTransaction()]] call.
824
     */
825 8
    private function rollbackTransactionOnLevel($transaction, $level)
826
    {
827 8
        if ($transaction->isActive && $transaction->level === $level) {
828
            // https://github.com/yiisoft/yii2/pull/13347
829
            try {
830 8
                $transaction->rollBack();
831
            } catch (\Exception $e) {
832
                \Yii::error($e, __METHOD__);
833
                // hide this exception to be able to continue throwing original exception outside
834
            }
835
        }
836 8
    }
837
838
    /**
839
     * Returns the schema information for the database opened by this connection.
840
     * @return Schema the schema information for the database opened by this connection.
841
     * @throws NotSupportedException if there is no support for the current driver type
842
     */
843 2087
    public function getSchema()
844
    {
845 2087
        if ($this->_schema !== null) {
846 1708
            return $this->_schema;
847
        }
848
849 2032
        $driver = $this->getDriverName();
850 2032
        if (isset($this->schemaMap[$driver])) {
851 2032
            $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver];
852 2032
            $config['db'] = $this;
853
854 2032
            return $this->_schema = Yii::createObject($config);
855
        }
856
857
        throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
858
    }
859
860
    /**
861
     * Returns the query builder for the current DB connection.
862
     * @return QueryBuilder the query builder for the current DB connection.
863
     */
864 1112
    public function getQueryBuilder()
865
    {
866 1112
        return $this->getSchema()->getQueryBuilder();
867
    }
868
869
    /**
870
     * Can be used to set [[QueryBuilder]] configuration via Connection configuration array.
871
     *
872
     * @param array $value the [[QueryBuilder]] properties to be configured.
873
     * @since 2.0.14
874
     */
875
    public function setQueryBuilder($value)
876
    {
877
        Yii::configure($this->getQueryBuilder(), $value);
878
    }
879
880
    /**
881
     * Obtains the schema information for the named table.
882
     * @param string $name table name.
883
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
884
     * @return TableSchema table schema information. Null if the named table does not exist.
885
     */
886 245
    public function getTableSchema($name, $refresh = false)
887
    {
888 245
        return $this->getSchema()->getTableSchema($name, $refresh);
889
    }
890
891
    /**
892
     * Returns the ID of the last inserted row or sequence value.
893
     * @param string $sequenceName name of the sequence object (required by some DBMS)
894
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
895
     * @see https://secure.php.net/manual/en/pdo.lastinsertid.php
896
     */
897 6
    public function getLastInsertID($sequenceName = '')
898
    {
899 6
        return $this->getSchema()->getLastInsertID($sequenceName);
900
    }
901
902
    /**
903
     * Quotes a string value for use in a query.
904
     * Note that if the parameter is not a string, it will be returned without change.
905
     * @param string $value string to be quoted
906
     * @return string the properly quoted string
907
     * @see https://secure.php.net/manual/en/pdo.quote.php
908
     */
909 1095
    public function quoteValue($value)
910
    {
911 1095
        return $this->getSchema()->quoteValue($value);
912
    }
913
914
    /**
915
     * Quotes a table name for use in a query.
916
     * If the table name contains schema prefix, the prefix will also be properly quoted.
917
     * If the table name is already quoted or contains special characters including '(', '[[' and '{{',
918
     * then this method will do nothing.
919
     * @param string $name table name
920
     * @return string the properly quoted table name
921
     */
922 1376
    public function quoteTableName($name)
923
    {
924 1376
        if (isset($this->_quotedTableNames[$name])) {
925 1020
            return $this->_quotedTableNames[$name];
926
        }
927 1310
        return $this->_quotedTableNames[$name] = $this->getSchema()->quoteTableName($name);
928
    }
929
930
    /**
931
     * Quotes a column name for use in a query.
932
     * If the column name contains prefix, the prefix will also be properly quoted.
933
     * If the column name is already quoted or contains special characters including '(', '[[' and '{{',
934
     * then this method will do nothing.
935
     * @param string $name column name
936
     * @return string the properly quoted column name
937
     */
938 1439
    public function quoteColumnName($name)
939
    {
940 1439
        if (isset($this->_quotedColumnNames[$name])) {
941 891
            return $this->_quotedColumnNames[$name];
942
        }
943 1384
        return $this->_quotedColumnNames[$name] = $this->getSchema()->quoteColumnName($name);
944
    }
945
946
    /**
947
     * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
948
     * Tokens enclosed within double curly brackets are treated as table names, while
949
     * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
950
     * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
951
     * with [[tablePrefix]].
952
     * @param string $sql the SQL to be quoted
953
     * @return string the quoted SQL
954
     */
955 1662
    public function quoteSql($sql)
956
    {
957 1662
        return preg_replace_callback(
958 1662
            '/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
959 1662
            function ($matches) {
960 871
                if (isset($matches[3])) {
961 651
                    return $this->quoteColumnName($matches[3]);
962
                }
963
964 737
                return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
965 1662
            },
966 1662
            $sql
967
        );
968
    }
969
970
    /**
971
     * Returns the name of the DB driver. Based on the the current [[dsn]], in case it was not set explicitly
972
     * by an end user.
973
     * @return string name of the DB driver
974
     */
975 2321
    public function getDriverName()
976
    {
977 2321
        if ($this->_driverName === null) {
978 2252
            if (($pos = strpos($this->dsn, ':')) !== false) {
979 2252
                $this->_driverName = strtolower(substr($this->dsn, 0, $pos));
980
            } else {
981
                $this->_driverName = strtolower($this->getSlavePdo()->getAttribute(PDO::ATTR_DRIVER_NAME));
982
            }
983
        }
984
985 2321
        return $this->_driverName;
986
    }
987
988
    /**
989
     * Changes the current driver name.
990
     * @param string $driverName name of the DB driver
991
     */
992
    public function setDriverName($driverName)
993
    {
994
        $this->_driverName = strtolower($driverName);
995
    }
996
997
    /**
998
     * Returns a server version as a string comparable by [[\version_compare()]].
999
     * @return string server version as a string.
1000
     * @since 2.0.14
1001
     */
1002 417
    public function getServerVersion()
1003
    {
1004 417
        return $this->getSchema()->getServerVersion();
1005
    }
1006
1007
    /**
1008
     * Returns the PDO instance for the currently active slave connection.
1009
     * When [[enableSlaves]] is true, one of the slaves will be used for read queries, and its PDO instance
1010
     * will be returned by this method.
1011
     * @param bool $fallbackToMaster whether to return a master PDO in case none of the slave connections is available.
1012
     * @return PDO the PDO instance for the currently active slave connection. `null` is returned if no slave connection
1013
     * is available and `$fallbackToMaster` is false.
1014
     */
1015 1770
    public function getSlavePdo($fallbackToMaster = true)
1016
    {
1017 1770
        $db = $this->getSlave(false);
1018 1770
        if ($db === null) {
1019 1766
            return $fallbackToMaster ? $this->getMasterPdo() : null;
1020
        }
1021
1022 5
        return $db->pdo;
1023
    }
1024
1025
    /**
1026
     * Returns the PDO instance for the currently active master connection.
1027
     * This method will open the master DB connection and then return [[pdo]].
1028
     * @return PDO the PDO instance for the currently active master connection.
1029
     */
1030 1801
    public function getMasterPdo()
1031
    {
1032 1801
        $this->open();
1033 1801
        return $this->pdo;
1034
    }
1035
1036
    /**
1037
     * Returns the currently active slave connection.
1038
     * If this method is called for the first time, it will try to open a slave connection when [[enableSlaves]] is true.
1039
     * @param bool $fallbackToMaster whether to return a master connection in case there is no slave connection available.
1040
     * @return Connection the currently active slave connection. `null` is returned if there is no slave available and
1041
     * `$fallbackToMaster` is false.
1042
     */
1043 1772
    public function getSlave($fallbackToMaster = true)
1044
    {
1045 1772
        if (!$this->enableSlaves) {
1046 221
            return $fallbackToMaster ? $this : null;
1047
        }
1048
1049 1667
        if ($this->_slave === false) {
1050 1667
            $this->_slave = $this->openFromPool($this->slaves, $this->slaveConfig);
1051
        }
1052
1053 1661
        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...
1054
    }
1055
1056
    /**
1057
     * Returns the currently active master connection.
1058
     * If this method is called for the first time, it will try to open a master connection.
1059
     * @return Connection the currently active master connection. `null` is returned if there is no master available.
1060
     * @since 2.0.11
1061
     */
1062 11
    public function getMaster()
1063
    {
1064 11
        if ($this->_master === false) {
1065 11
            $this->_master = $this->shuffleMasters
1066 2
                ? $this->openFromPool($this->masters, $this->masterConfig)
1067 9
                : $this->openFromPoolSequentially($this->masters, $this->masterConfig);
1068
        }
1069
1070 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...
1071
    }
1072
1073
    /**
1074
     * Executes the provided callback by using the master connection.
1075
     *
1076
     * This method is provided so that you can temporarily force using the master connection to perform
1077
     * DB operations even if they are read queries. For example,
1078
     *
1079
     * ```php
1080
     * $result = $db->useMaster(function ($db) {
1081
     *     return $db->createCommand('SELECT * FROM user LIMIT 1')->queryOne();
1082
     * });
1083
     * ```
1084
     *
1085
     * @param callable $callback a PHP callable to be executed by this method. Its signature is
1086
     * `function (Connection $db)`. Its return value will be returned by this method.
1087
     * @return mixed the return value of the callback
1088
     * @throws \Exception|\Throwable if there is any exception thrown from the callback
1089
     */
1090 112
    public function useMaster(callable $callback)
1091
    {
1092 112
        if ($this->enableSlaves) {
1093 106
            $this->enableSlaves = false;
1094
            try {
1095 106
                $result = call_user_func($callback, $this);
1096 4
            } catch (\Exception $e) {
1097 4
                $this->enableSlaves = true;
1098 4
                throw $e;
1099
            } catch (\Throwable $e) {
1100
                $this->enableSlaves = true;
1101
                throw $e;
1102
            }
1103
            // TODO: use "finally" keyword when miminum required PHP version is >= 5.5
1104 102
            $this->enableSlaves = true;
1105
        } else {
1106 6
            $result = call_user_func($callback, $this);
1107
        }
1108
1109 108
        return $result;
1110
    }
1111
1112
    /**
1113
     * Opens the connection to a server in the pool.
1114
     *
1115
     * This method implements load balancing and failover among the given list of the servers.
1116
     * Connections will be tried in random order.
1117
     * For details about the failover behavior, see [[openFromPoolSequentially]].
1118
     *
1119
     * @param array $pool the list of connection configurations in the server pool
1120
     * @param array $sharedConfig the configuration common to those given in `$pool`.
1121
     * @return Connection the opened DB connection, or `null` if no server is available
1122
     * @throws InvalidConfigException if a configuration does not specify "dsn"
1123
     * @see openFromPoolSequentially
1124
     */
1125 1667
    protected function openFromPool(array $pool, array $sharedConfig)
1126
    {
1127 1667
        shuffle($pool);
1128 1667
        return $this->openFromPoolSequentially($pool, $sharedConfig);
1129
    }
1130
1131
    /**
1132
     * Opens the connection to a server in the pool.
1133
     *
1134
     * This method implements failover among the given list of servers.
1135
     * Connections will be tried in sequential order. The first successful connection will return.
1136
     *
1137
     * If [[serverStatusCache]] is configured, this method will cache information about
1138
     * unreachable servers and does not try to connect to these for the time configured in [[serverRetryInterval]].
1139
     * This helps to keep the application stable when some servers are unavailable. Avoiding
1140
     * connection attempts to unavailable servers saves time when the connection attempts fail due to timeout.
1141
     *
1142
     * If none of the servers are available the status cache is ignored and connection attempts are made to all
1143
     * servers (Since version 2.0.35). This is to avoid downtime when all servers are unavailable for a short time.
1144
     * After a successful connection attempt the server is marked as available again.
1145
     *
1146
     * @param array $pool the list of connection configurations in the server pool
1147
     * @param array $sharedConfig the configuration common to those given in `$pool`.
1148
     * @return Connection the opened DB connection, or `null` if no server is available
1149
     * @throws InvalidConfigException if a configuration does not specify "dsn"
1150
     * @since 2.0.11
1151
     * @see openFromPool
1152
     * @see serverStatusCache
1153
     */
1154 1675
    protected function openFromPoolSequentially(array $pool, array $sharedConfig)
1155
    {
1156 1675
        if (empty($pool)) {
1157 1655
            return null;
1158
        }
1159
1160 21
        if (!isset($sharedConfig['class'])) {
1161 21
            $sharedConfig['class'] = get_class($this);
1162
        }
1163
1164 21
        $cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache;
1165
1166 21
        foreach ($pool as $i => $config) {
1167 21
            $pool[$i] = $config = array_merge($sharedConfig, $config);
1168 21
            if (empty($config['dsn'])) {
1169 6
                throw new InvalidConfigException('The "dsn" option must be specified.');
1170
            }
1171
1172 15
            $key = [__METHOD__, $config['dsn']];
1173 15
            if ($cache instanceof CacheInterface && $cache->get($key)) {
1174
                // should not try this dead server now
1175
                continue;
1176
            }
1177
1178
            /* @var $db Connection */
1179 15
            $db = Yii::createObject($config);
1180
1181
            try {
1182 15
                $db->open();
1183 15
                return $db;
1184 8
            } catch (\Exception $e) {
1185 8
                Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
1186 8
                if ($cache instanceof CacheInterface) {
1187
                    // mark this server as dead and only retry it after the specified interval
1188 4
                    $cache->set($key, 1, $this->serverRetryInterval);
1189
                }
1190
                // exclude server from retry below
1191 8
                unset($pool[$i]);
1192
            }
1193
        }
1194
1195 8
        if ($cache instanceof CacheInterface) {
1196
            // if server status cache is enabled and no server is available
1197
            // ignore the cache and try to connect anyway
1198
            // $pool now only contains servers we did not already try in the loop above
1199 4
            foreach ($pool as $config) {
1200
1201
                /* @var $db Connection */
1202
                $db = Yii::createObject($config);
1203
                try {
1204
                    $db->open();
1205
                } catch (\Exception $e) {
1206
                    Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
1207
                    continue;
1208
                }
1209
1210
                // mark this server as available again after successful connection
1211
                $cache->delete([__METHOD__, $config['dsn']]);
1212
1213
                return $db;
1214
            }
1215
        }
1216
1217 8
        return null;
1218
    }
1219
1220
    /**
1221
     * Close the connection before serializing.
1222
     * @return array
1223
     */
1224 19
    public function __sleep()
1225
    {
1226 19
        $fields = (array) $this;
1227
1228 19
        unset($fields['pdo']);
1229 19
        unset($fields["\000" . __CLASS__ . "\000" . '_master']);
1230 19
        unset($fields["\000" . __CLASS__ . "\000" . '_slave']);
1231 19
        unset($fields["\000" . __CLASS__ . "\000" . '_transaction']);
1232 19
        unset($fields["\000" . __CLASS__ . "\000" . '_schema']);
1233
1234 19
        return array_keys($fields);
1235
    }
1236
1237
    /**
1238
     * Reset the connection after cloning.
1239
     */
1240 7
    public function __clone()
1241
    {
1242 7
        parent::__clone();
1243
1244 7
        $this->_master = false;
1245 7
        $this->_slave = false;
1246 7
        $this->_schema = null;
1247 7
        $this->_transaction = null;
1248 7
        if (strncmp($this->dsn, 'sqlite::memory:', 15) !== 0) {
1249
            // reset PDO connection, unless its sqlite in-memory, which can only have one connection
1250 6
            $this->pdo = null;
1251
        }
1252 7
    }
1253
}
1254