Passed
Push — master ( 9dbdd9...d5a428 )
by Alexander
04:15
created

framework/db/Connection.php (2 issues)

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 array An array of [[setQueryBuilder()]] calls, holding the passed arguments.
429
     * Is used to restore a QueryBuilder configuration after the connection close/open cycle.
430
     *
431
     * @see restoreQueryBuilderConfiguration()
432
     */
433
    private $_queryBuilderConfigurations = [];
434
    /**
435
     * @var Transaction the currently active transaction
436
     */
437
    private $_transaction;
438
    /**
439
     * @var Schema the database schema
440
     */
441
    private $_schema;
442
    /**
443
     * @var string driver name
444
     */
445
    private $_driverName;
446
    /**
447
     * @var Connection|false the currently active master connection
448
     */
449
    private $_master = false;
450
    /**
451
     * @var Connection|false the currently active slave connection
452
     */
453
    private $_slave = false;
454
    /**
455
     * @var array query cache parameters for the [[cache()]] calls
456
     */
457
    private $_queryCacheInfo = [];
458
    /**
459
     * @var string[] quoted table name cache for [[quoteTableName()]] calls
460
     */
461
    private $_quotedTableNames;
462
    /**
463
     * @var string[] quoted column name cache for [[quoteColumnName()]] calls
464
     */
465
    private $_quotedColumnNames;
466
467
468
    /**
469
     * Returns a value indicating whether the DB connection is established.
470
     * @return bool whether the DB connection is established
471
     */
472 360
    public function getIsActive()
473
    {
474 360
        return $this->pdo !== null;
475
    }
476
477
    /**
478
     * Uses query cache for the queries performed with the callable.
479
     *
480
     * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache),
481
     * queries performed within the callable will be cached and their results will be fetched from cache if available.
482
     * For example,
483
     *
484
     * ```php
485
     * // The customer will be fetched from cache if available.
486
     * // If not, the query will be made against DB and cached for use next time.
487
     * $customer = $db->cache(function (Connection $db) {
488
     *     return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
489
     * });
490
     * ```
491
     *
492
     * Note that query cache is only meaningful for queries that return results. For queries performed with
493
     * [[Command::execute()]], query cache will not be used.
494
     *
495
     * @param callable $callable a PHP callable that contains DB queries which will make use of query cache.
496
     * The signature of the callable is `function (Connection $db)`.
497
     * @param int $duration the number of seconds that query results can remain valid in the cache. If this is
498
     * not set, the value of [[queryCacheDuration]] will be used instead.
499
     * Use 0 to indicate that the cached data will never expire.
500
     * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
501
     * @return mixed the return result of the callable
502
     * @throws \Exception|\Throwable if there is any exception during query
503
     * @see enableQueryCache
504
     * @see queryCache
505
     * @see noCache()
506
     */
507 10
    public function cache(callable $callable, $duration = null, $dependency = null)
508
    {
509 10
        $this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency];
510
        try {
511 10
            $result = call_user_func($callable, $this);
512 10
            array_pop($this->_queryCacheInfo);
513 10
            return $result;
514
        } catch (\Exception $e) {
515
            array_pop($this->_queryCacheInfo);
516
            throw $e;
517
        } catch (\Throwable $e) {
518
            array_pop($this->_queryCacheInfo);
519
            throw $e;
520
        }
521
    }
522
523
    /**
524
     * Disables query cache temporarily.
525
     *
526
     * Queries performed within the callable will not use query cache at all. For example,
527
     *
528
     * ```php
529
     * $db->cache(function (Connection $db) {
530
     *
531
     *     // ... queries that use query cache ...
532
     *
533
     *     return $db->noCache(function (Connection $db) {
534
     *         // this query will not use query cache
535
     *         return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
536
     *     });
537
     * });
538
     * ```
539
     *
540
     * @param callable $callable a PHP callable that contains DB queries which should not use query cache.
541
     * The signature of the callable is `function (Connection $db)`.
542
     * @return mixed the return result of the callable
543
     * @throws \Exception|\Throwable if there is any exception during query
544
     * @see enableQueryCache
545
     * @see queryCache
546
     * @see cache()
547
     */
548 78
    public function noCache(callable $callable)
549
    {
550 78
        $this->_queryCacheInfo[] = false;
551
        try {
552 78
            $result = call_user_func($callable, $this);
553 78
            array_pop($this->_queryCacheInfo);
554 78
            return $result;
555 8
        } catch (\Exception $e) {
556 8
            array_pop($this->_queryCacheInfo);
557 8
            throw $e;
558
        } catch (\Throwable $e) {
559
            array_pop($this->_queryCacheInfo);
560
            throw $e;
561
        }
562
    }
563
564
    /**
565
     * Returns the current query cache information.
566
     * This method is used internally by [[Command]].
567
     * @param int $duration the preferred caching duration. If null, it will be ignored.
568
     * @param \yii\caching\Dependency $dependency the preferred caching dependency. If null, it will be ignored.
569
     * @return array the current query cache information, or null if query cache is not enabled.
570
     * @internal
571
     */
572 2310
    public function getQueryCacheInfo($duration, $dependency)
573
    {
574 2310
        if (!$this->enableQueryCache) {
575 82
            return null;
576
        }
577
578 2308
        $info = end($this->_queryCacheInfo);
579 2308
        if (is_array($info)) {
580 10
            if ($duration === null) {
581 10
                $duration = $info[0];
582
            }
583 10
            if ($dependency === null) {
584 10
                $dependency = $info[1];
585
            }
586
        }
587
588 2308
        if ($duration === 0 || $duration > 0) {
589 10
            if (is_string($this->queryCache) && Yii::$app) {
590
                $cache = Yii::$app->get($this->queryCache, false);
591
            } else {
592 10
                $cache = $this->queryCache;
593
            }
594 10
            if ($cache instanceof CacheInterface) {
595 10
                return [$cache, $duration, $dependency];
596
            }
597
        }
598
599 2308
        return null;
600
    }
601
602
    /**
603
     * Establishes a DB connection.
604
     * It does nothing if a DB connection has already been established.
605
     * @throws Exception if connection fails
606
     */
607 3073
    public function open()
608
    {
609 3073
        if ($this->pdo !== null) {
610 2446
            return;
611
        }
612
613 3006
        if (!empty($this->masters)) {
614 13
            $db = $this->getMaster();
615 13
            if ($db !== null) {
616 13
                $this->pdo = $db->pdo;
617 13
                return;
618
            }
619
620 12
            throw new InvalidConfigException('None of the master DB servers is available.');
621
        }
622
623 3006
        if (empty($this->dsn)) {
624
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
625
        }
626
627 3006
        $token = 'Opening DB connection: ' . $this->dsn;
628 3006
        $enableProfiling = $this->enableProfiling;
629
        try {
630 3006
            if ($this->enableLogging) {
631 3006
                Yii::info($token, __METHOD__);
632
            }
633
634 3006
            if ($enableProfiling) {
635 3006
                Yii::beginProfile($token, __METHOD__);
636
            }
637
638 3006
            $this->pdo = $this->createPdoInstance();
639 3006
            $this->initConnection();
640
641 3006
            if ($enableProfiling) {
642 3006
                Yii::endProfile($token, __METHOD__);
643
            }
644 18
        } catch (\PDOException $e) {
645 18
            if ($enableProfiling) {
646 18
                Yii::endProfile($token, __METHOD__);
647
            }
648
649 18
            throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
650
        }
651 3006
    }
652
653
    /**
654
     * Closes the currently active DB connection.
655
     * It does nothing if the connection is already closed.
656
     */
657 3399
    public function close()
658
    {
659 3399
        if ($this->_master) {
660 12
            if ($this->pdo === $this->_master->pdo) {
661 12
                $this->pdo = null;
662
            }
663
664 12
            $this->_master->close();
665 12
            $this->_master = false;
666
        }
667
668 3399
        if ($this->pdo !== null) {
669 2744
            Yii::debug('Closing DB connection: ' . $this->dsn, __METHOD__);
670 2744
            $this->pdo = null;
671
        }
672
673 3399
        if ($this->_slave) {
674 6
            $this->_slave->close();
675 6
            $this->_slave = false;
676
        }
677
678 3399
        $this->_schema = null;
679 3399
        $this->_transaction = null;
680 3399
        $this->_driverName = null;
681 3399
        $this->_queryCacheInfo = [];
682 3399
        $this->_quotedTableNames = null;
683 3399
        $this->_quotedColumnNames = null;
684 3399
    }
685
686
    /**
687
     * Creates the PDO instance.
688
     * This method is called by [[open]] to establish a DB connection.
689
     * The default implementation will create a PHP PDO instance.
690
     * You may override this method if the default PDO needs to be adapted for certain DBMS.
691
     * @return PDO the pdo instance
692
     */
693 3006
    protected function createPdoInstance()
694
    {
695 3006
        $pdoClass = $this->pdoClass;
696 3006
        if ($pdoClass === null) {
697 3006
            $driver = null;
698 3006
            if ($this->_driverName !== null) {
699 340
                $driver = $this->_driverName;
700 2679
            } elseif (($pos = strpos($this->dsn, ':')) !== false) {
701 2679
                $driver = strtolower(substr($this->dsn, 0, $pos));
702
            }
703 3006
            switch ($driver) {
704 3006
                case 'mssql':
705
                    $pdoClass = 'yii\db\mssql\PDO';
706 3006
                    break;
707
                case 'dblib':
708 3006
                    $pdoClass = 'yii\db\mssql\DBLibPDO';
709 978
                    break;
710
                case 'sqlsrv':
711
                    $pdoClass = 'yii\db\mssql\SqlsrvPDO';
712
                    break;
713
                default:
714 3006
                    $pdoClass = 'PDO';
715 3006
            }
716 1
        }
717
718
        $dsn = $this->dsn;
719 3006
        if (strncmp('sqlite:@', $dsn, 8) === 0) {
720
            $dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7));
721
        }
722
723
        return new $pdoClass($dsn, $this->username, $this->password, $this->attributes);
724
    }
725
726
    /**
727
     * Initializes the DB connection.
728
     * This method is invoked right after the DB connection is established.
729 3006
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
730
     * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
731 3006
     * It then triggers an [[EVENT_AFTER_OPEN]] event.
732 3006
     */
733
    protected function initConnection()
734
    {
735
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
736
        if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
737 3006
            if ($this->driverName !== 'sqlsrv') {
738
                $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
739
            }
740 3006
        }
741
        if (!$this->isSybase && in_array($this->getDriverName(), ['mssql', 'dblib'], true)) {
742
            $this->pdo->exec('SET ANSI_NULL_DFLT_ON ON');
743 3006
        }
744 3006
        if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'], true)) {
745
            $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
746
        }
747
        $this->trigger(self::EVENT_AFTER_OPEN);
748
    }
749
750
    /**
751
     * Creates a command for execution.
752 2450
     * @param string $sql the SQL statement to be executed
753
     * @param array $params the parameters to be bound to the SQL statement
754 2450
     * @return Command the DB command
755 2450
     */
756 2450
    public function createCommand($sql = null, $params = [])
757
    {
758 2450
        $driver = $this->getDriverName();
759 2450
        $config = ['class' => 'yii\db\Command'];
760
        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

760
        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...
761 2450
            $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

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