Passed
Push — master ( 9a3093...472600 )
by Alexander
09:50
created

Connection::usePrimary()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 20
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.4661

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 4
nop 1
dl 0
loc 20
ccs 9
cts 13
cp 0.6923
crap 4.4661
rs 9.7998
c 0
b 0
f 0
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\db;
9
10
use PDO;
11
use Yii;
12
use yii\base\Component;
13
use yii\base\InvalidConfigException;
14
use yii\base\NotSupportedException;
15
use yii\caching\CacheInterface;
16
17
/**
18
 * Connection represents a connection to a database via [PDO](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 [[primaries]] and [[replicas]]. It will do load balancing and failover by choosing
26
 * appropriate servers. It will also automatically direct read operations to the replicas and write operations to
27
 * the primary connections.
28
 *
29
 * To establish a DB connection, set [[dsn]], [[username]] and [[password]], and then
30
 * call [[open()]] to connect to the database server. The current state of the connection can be checked using [[$isActive]].
31
 *
32
 * The following example shows how to create a Connection instance and establish
33
 * the DB connection:
34
 *
35
 * ```php
36
 * $connection = new \yii\db\Connection([
37
 *     'dsn' => $dsn,
38
 *     'username' => $username,
39
 *     'password' => $password,
40
 * ]);
41
 * $connection->open();
42
 * ```
43
 *
44
 * After the DB connection is established, one can execute SQL statements like the following:
45
 *
46
 * ```php
47
 * $command = $connection->createCommand('SELECT * FROM post');
48
 * $posts = $command->queryAll();
49
 * $command = $connection->createCommand('UPDATE post SET status=1');
50
 * $command->execute();
51
 * ```
52
 *
53
 * One can also do prepared SQL execution and bind parameters to the prepared SQL.
54
 * When the parameters are coming from user input, you should use this approach
55
 * to prevent SQL injection attacks. The following is an example:
56
 *
57
 * ```php
58
 * $command = $connection->createCommand('SELECT * FROM post WHERE id=:id');
59
 * $command->bindValue(':id', $_GET['id']);
60
 * $post = $command->query();
61
 * ```
62
 *
63
 * For more information about how to perform various DB queries, please refer to [[Command]].
64
 *
65
 * If the underlying DBMS supports transactions, you can perform transactional SQL queries
66
 * like the following:
67
 *
68
 * ```php
69
 * $transaction = $connection->beginTransaction();
70
 * try {
71
 *     $connection->createCommand($sql1)->execute();
72
 *     $connection->createCommand($sql2)->execute();
73
 *     // ... executing other SQL statements ...
74
 *     $transaction->commit();
75
 * } catch (Exception $e) {
76
 *     $transaction->rollBack();
77
 * }
78
 * ```
79
 *
80
 * You also can use shortcut for the above like the following:
81
 *
82
 * ```php
83
 * $connection->transaction(function () {
84
 *     $order = new Order($customer);
85
 *     $order->save();
86
 *     $order->addItems($items);
87
 * });
88
 * ```
89
 *
90
 * If needed you can pass transaction isolation level as a second parameter:
91
 *
92
 * ```php
93
 * $connection->transaction(function (Connection $db) {
94
 *     //return $db->...
95
 * }, Transaction::READ_UNCOMMITTED);
96
 * ```
97
 *
98
 * Connection is often used as an application component and configured in the application
99
 * configuration like the following:
100
 *
101
 * ```php
102
 * 'components' => [
103
 *     'db' => [
104
 *         'class' => '\yii\db\Connection',
105
 *         'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
106
 *         'username' => 'root',
107
 *         'password' => '',
108
 *         'charset' => 'utf8',
109
 *     ],
110
 * ],
111
 * ```
112
 *
113
 * @property string $driverName Name of the DB driver.
114
 * @property bool $isActive Whether the DB connection is established. This property is read-only.
115
 * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
116
 * sequence object. This property is read-only.
117
 * @property-read Connection|null $primary The currently active primary connection. `null` is returned if no primary
118
 * connection is available. This property is read-only.
119
 * @property-read PDO $primaryPdo The PDO instance for the currently active primary connection. This property is
120
 * read-only.
121
 * @property QueryBuilder $queryBuilder The query builder for the current DB connection. Note that the type of
122
 * this property differs in getter and setter. See [[getQueryBuilder()]] and [[setQueryBuilder()]] for details.
123
 * @property Schema $schema The schema information for the database opened by this connection. This property
124
 * is read-only.
125
 * @property string $serverVersion Server version as a string. This property is read-only.
126
 * @property-read Connection $replica The currently active replica connection. This property is read-only.
127
 * @property-read PDO $replicaPdo The PDO instance for the currently active replica connection. This property
128
 * is read-only.
129
 * @property Transaction|null $transaction The currently active transaction. Null if no active transaction.
130
 * This property is read-only.
131
 * @mixin ConnectionDeprecationsTrait
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 [[primaries]] and [[replicas]].
333
     * This is used only when read/write splitting is enabled or [[primaries]] 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 [[primaries]] and [[replicas]].
341
     * This is used together with [[serverStatusCache]].
342
     */
343
    public $serverRetryInterval = 600;
344
    /**
345
     * @var bool whether to enable read/write splitting by using [[replicas]] to read data.
346
     * Note that if [[replicas]] is empty, read/write splitting will NOT be enabled no matter what value this property takes.
347
     * @since 2.0.36
348
     */
349
    public $enableReplicas = true;
350
    /**
351
     * Returns the value of [[enableReplicas]].
352
     * @return bool
353
     * @deprecated since 2.0.36. Use [[enableReplicas]] instead.
354
     */
355
    public function getEnableSlaves()
356
    {
357
        return $this->enableReplicas;
358
    }
359
    /**
360
     * Sets the value of [[enableReplicas]].
361
     * @param bool $value
362
     * @deprecated since 2.0.36. Use [[enableReplicas]] instead.
363
     */
364
    public function setEnableSlaves($value)
365
    {
366
        $this->enableReplicas = $value;
367
    }
368
    /**
369
     * @var array list of replica connection configurations. Each configuration is used to create a replica DB connection.
370
     * When [[enableReplicas]] is true, one of these configurations will be chosen and used to create a DB connection
371
     * for performing read queries only.
372
     * @see enableReplicas
373
     * @see replicaConfig
374
     * @since 2.0.36
375
     */
376
    public $replicas = [];
377
    /**
378
     * Returns the value of [[replicas]].
379
     * @return array
380
     * @deprecated since 2.0.36. Use [[replicas]] instead.
381
     */
382
    public function getSlaves()
383
    {
384
        return $this->replicas;
385
    }
386
    /**
387
     * Sets the value of [[replicas]].
388
     * @param array $value
389
     * @deprecated since 2.0.36. Use [[replicas]] instead.
390
     */
391
    public function setSlaves($value)
392
    {
393
        $this->replicas = $value;
394
    }
395
    /**
396
     * @var array the configuration that should be merged with every replica configuration listed in [[replicas]].
397
     * For example,
398
     *
399
     * ```php
400
     * [
401
     *     'username' => 'replica',
402
     *     'password' => 'replica',
403
     *     'attributes' => [
404
     *         // use a smaller connection timeout
405
     *         PDO::ATTR_TIMEOUT => 10,
406
     *     ],
407
     * ]
408
     * ```
409
     *
410
     * @since 2.0.36
411
     */
412
    public $replicaConfig = [];
413
    /**
414
     * Returns the value of [[replicaConfig]].
415
     * @return array
416
     * @deprecated since 2.0.36. Use [[replicaConfig]] instead.
417
     */
418
    public function getSlaveConfig()
419
    {
420
        return $this->replicaConfig;
421
    }
422
    /**
423
     * Sets the value of [[replicaConfig]].
424
     * @param array $value
425
     * @deprecated since 2.0.36. Use [[replicaConfig]] instead.
426
     */
427
    public function setSlaveConfig($value)
428
    {
429
        $this->replicaConfig = $value;
430
    }
431
    /**
432
     * @var array list of primary connection configurations. Each configuration is used to create a primary DB connection.
433
     * When [[open()]] is called, one of these configurations will be chosen and used to create a DB connection
434
     * which will be used by this object.
435
     * Note that when this property is not empty, the connection setting (e.g. `dsn`, `username`) of this object will
436
     * be ignored.
437
     * @see primaryConfig
438
     * @see shufflePrimaries
439
     * @since 2.0.36
440
     */
441
    public $primaries = [];
442
    /**
443
     * Returns the value of [[primaries]].
444
     * @return array
445
     * @deprecated since 2.0.36. Use [[primaries]] instead.
446
     */
447
    public function getMasters()
448
    {
449
        return $this->primaries;
450
    }
451
    /**
452
     * Sets the value of [[primaries]].
453
     * @param array $value
454
     * @deprecated since 2.0.36. Use [[primaries]] instead.
455
     */
456
    public function setMasters($value)
457
    {
458
        $this->primaries = $value;
459
    }
460
    /**
461
     * @var array the configuration that should be merged with every primary configuration listed in [[primaries]].
462
     * For example,
463
     *
464
     * ```php
465
     * [
466
     *     'username' => 'primary',
467
     *     'password' => 'primary',
468
     *     'attributes' => [
469
     *         // use a smaller connection timeout
470
     *         PDO::ATTR_TIMEOUT => 10,
471
     *     ],
472
     * ]
473
     * ```
474
     *
475
     * @since 2.0.36
476
     */
477
    public $primaryConfig = [];
478
    /**
479
     * Returns the value of [[primaryConfig]].
480
     * @return array
481
     * @deprecated since 2.0.36. Use [[primaryConfig]] instead.
482
     */
483
    public function getMasterConfig()
484
    {
485
        return $this->primaryConfig;
486
    }
487
    /**
488
     * Sets the value of [[primaryConfig]].
489
     * @param array $value
490
     * @deprecated since 2.0.36. Use [[primaryConfig]] instead.
491
     */
492
    public function setMasterConfig($value)
493
    {
494
        $this->primaryConfig = $value;
495
    }
496
    /**
497
     * @var bool whether to shuffle [[primaries]] before getting one.
498
     * @since 2.0.11
499
     * @see primaries
500
     * @since 2.0.36
501
     */
502
    public $shufflePrimaries = true;
503
    /**
504
     * Returns the value of [[shufflePrimaries]].
505
     * @return bool
506
     * @deprecated since 2.0.36. Use [[shufflePrimaries]] instead.
507
     */
508
    public function getShuffleMasters()
509
    {
510
        return $this->shufflePrimaries;
511
    }
512
    /**
513
     * Sets the value of [[shufflePrimaries]].
514
     * @param bool $value
515
     * @deprecated since 2.0.36. Use [[shufflePrimaries]] instead.
516
     */
517
    public function setShuffleMasters($value)
518
    {
519
        $this->shufflePrimaries = $value;
520
    }
521
    /**
522
     * @var bool whether to enable logging of database queries. Defaults to true.
523
     * You may want to disable this option in a production environment to gain performance
524
     * if you do not need the information being logged.
525
     * @since 2.0.12
526
     * @see enableProfiling
527
     */
528
    public $enableLogging = true;
529
    /**
530
     * @var bool whether to enable profiling of opening database connection and database queries. Defaults to true.
531
     * You may want to disable this option in a production environment to gain performance
532
     * if you do not need the information being logged.
533
     * @since 2.0.12
534
     * @see enableLogging
535
     */
536
    public $enableProfiling = true;
537
538
    /**
539
     * @var Transaction the currently active transaction
540
     */
541
    private $_transaction;
542
    /**
543
     * @var Schema the database schema
544
     */
545
    private $_schema;
546
    /**
547
     * @var string driver name
548
     */
549
    private $_driverName;
550
    /**
551
     * @var Connection|false the currently active primary connection
552
     */
553
    private $_primary = false;
554
    /**
555
     * @var Connection|false the currently active replica connection
556
     */
557
    private $_replica = false;
558
    /**
559
     * @var array query cache parameters for the [[cache()]] calls
560
     */
561
    private $_queryCacheInfo = [];
562
    /**
563
     * @var string[] quoted table name cache for [[quoteTableName()]] calls
564
     */
565
    private $_quotedTableNames;
566
    /**
567
     * @var string[] quoted column name cache for [[quoteColumnName()]] calls
568
     */
569
    private $_quotedColumnNames;
570
571
572
    /**
573
     * Returns a value indicating whether the DB connection is established.
574
     * @return bool whether the DB connection is established
575
     */
576 332
    public function getIsActive()
577
    {
578 332
        return $this->pdo !== null;
579
    }
580
581
    /**
582
     * Uses query cache for the queries performed with the callable.
583
     *
584
     * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache),
585
     * queries performed within the callable will be cached and their results will be fetched from cache if available.
586
     * For example,
587
     *
588
     * ```php
589
     * // The customer will be fetched from cache if available.
590
     * // If not, the query will be made against DB and cached for use next time.
591
     * $customer = $db->cache(function (Connection $db) {
592
     *     return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
593
     * });
594
     * ```
595
     *
596
     * Note that query cache is only meaningful for queries that return results. For queries performed with
597
     * [[Command::execute()]], query cache will not be used.
598
     *
599
     * @param callable $callable a PHP callable that contains DB queries which will make use of query cache.
600
     * The signature of the callable is `function (Connection $db)`.
601
     * @param int $duration the number of seconds that query results can remain valid in the cache. If this is
602
     * not set, the value of [[queryCacheDuration]] will be used instead.
603
     * Use 0 to indicate that the cached data will never expire.
604
     * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
605
     * @return mixed the return result of the callable
606
     * @throws \Exception|\Throwable if there is any exception during query
607
     * @see enableQueryCache
608
     * @see queryCache
609
     * @see noCache()
610
     */
611 6
    public function cache(callable $callable, $duration = null, $dependency = null)
612
    {
613 6
        $this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency];
614
        try {
615 6
            $result = call_user_func($callable, $this);
616 6
            array_pop($this->_queryCacheInfo);
617 6
            return $result;
618
        } catch (\Exception $e) {
619
            array_pop($this->_queryCacheInfo);
620
            throw $e;
621
        } catch (\Throwable $e) {
622
            array_pop($this->_queryCacheInfo);
623
            throw $e;
624
        }
625
    }
626
627
    /**
628
     * Disables query cache temporarily.
629
     *
630
     * Queries performed within the callable will not use query cache at all. For example,
631
     *
632
     * ```php
633
     * $db->cache(function (Connection $db) {
634
     *
635
     *     // ... queries that use query cache ...
636
     *
637
     *     return $db->noCache(function (Connection $db) {
638
     *         // this query will not use query cache
639
     *         return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
640
     *     });
641
     * });
642
     * ```
643
     *
644
     * @param callable $callable a PHP callable that contains DB queries which should not use query cache.
645
     * The signature of the callable is `function (Connection $db)`.
646
     * @return mixed the return result of the callable
647
     * @throws \Exception|\Throwable if there is any exception during query
648
     * @see enableQueryCache
649
     * @see queryCache
650
     * @see cache()
651
     */
652 40
    public function noCache(callable $callable)
653
    {
654 40
        $this->_queryCacheInfo[] = false;
655
        try {
656 40
            $result = call_user_func($callable, $this);
657 40
            array_pop($this->_queryCacheInfo);
658 40
            return $result;
659 4
        } catch (\Exception $e) {
660 4
            array_pop($this->_queryCacheInfo);
661 4
            throw $e;
662
        } catch (\Throwable $e) {
663
            array_pop($this->_queryCacheInfo);
664
            throw $e;
665
        }
666
    }
667
668
    /**
669
     * Returns the current query cache information.
670
     * This method is used internally by [[Command]].
671
     * @param int $duration the preferred caching duration. If null, it will be ignored.
672
     * @param \yii\caching\Dependency $dependency the preferred caching dependency. If null, it will be ignored.
673
     * @return array the current query cache information, or null if query cache is not enabled.
674
     * @internal
675
     */
676 1505
    public function getQueryCacheInfo($duration, $dependency)
677
    {
678 1505
        if (!$this->enableQueryCache) {
679 44
            return null;
680
        }
681
682 1503
        $info = end($this->_queryCacheInfo);
683 1503
        if (is_array($info)) {
684 6
            if ($duration === null) {
0 ignored issues
show
introduced by
The condition $duration === null is always false.
Loading history...
685 6
                $duration = $info[0];
686
            }
687 6
            if ($dependency === null) {
688 6
                $dependency = $info[1];
689
            }
690
        }
691
692 1503
        if ($duration === 0 || $duration > 0) {
693 6
            if (is_string($this->queryCache) && Yii::$app) {
694
                $cache = Yii::$app->get($this->queryCache, false);
695
            } else {
696 6
                $cache = $this->queryCache;
697
            }
698 6
            if ($cache instanceof CacheInterface) {
699 6
                return [$cache, $duration, $dependency];
700
            }
701
        }
702
703 1503
        return null;
704
    }
705
706
    /**
707
     * Establishes a DB connection.
708
     * It does nothing if a DB connection has already been established.
709
     * @throws Exception if connection fails
710
     */
711 2034
    public function open()
712
    {
713 2034
        if ($this->pdo !== null) {
714 1615
            return;
715
        }
716
717 1979
        if (!empty($this->primaries)) {
718 9
            $db = $this->getPrimary();
719 9
            if ($db !== null) {
720 9
                $this->pdo = $db->pdo;
721 9
                return;
722
            }
723
724 8
            throw new InvalidConfigException('None of the primary DB servers are available.');
725
        }
726
727 1979
        if (empty($this->dsn)) {
728
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
729
        }
730
731 1979
        $token = 'Opening DB connection: ' . $this->dsn;
732 1979
        $enableProfiling = $this->enableProfiling;
733
        try {
734 1979
            if ($this->enableLogging) {
735 1979
                Yii::info($token, __METHOD__);
736
            }
737
738 1979
            if ($enableProfiling) {
739 1979
                Yii::beginProfile($token, __METHOD__);
740
            }
741
742 1979
            $this->pdo = $this->createPdoInstance();
743 1979
            $this->initConnection();
744
745 1979
            if ($enableProfiling) {
746 1979
                Yii::endProfile($token, __METHOD__);
747
            }
748 12
        } catch (\PDOException $e) {
749 12
            if ($enableProfiling) {
750 12
                Yii::endProfile($token, __METHOD__);
751
            }
752
753 12
            throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
754
        }
755 1979
    }
756
757
    /**
758
     * Closes the currently active DB connection.
759
     * It does nothing if the connection is already closed.
760
     */
761 2131
    public function close()
762
    {
763 2131
        if ($this->_primary) {
764 8
            if ($this->pdo === $this->_primary->pdo) {
765 8
                $this->pdo = null;
766
            }
767
768 8
            $this->_primary->close();
769 8
            $this->_primary = false;
770
        }
771
772 2131
        if ($this->pdo !== null) {
773 1784
            Yii::debug('Closing DB connection: ' . $this->dsn, __METHOD__);
774 1784
            $this->pdo = null;
775
        }
776
777 2131
        if ($this->_replica) {
778 4
            $this->_replica->close();
779 4
            $this->_replica = false;
780
        }
781
782 2131
        $this->_schema = null;
783 2131
        $this->_transaction = null;
784 2131
        $this->_driverName = null;
785 2131
        $this->_queryCacheInfo = [];
786 2131
        $this->_quotedTableNames = null;
787 2131
        $this->_quotedColumnNames = null;
788 2131
    }
789
790
    /**
791
     * Creates the PDO instance.
792
     * This method is called by [[open]] to establish a DB connection.
793
     * The default implementation will create a PHP PDO instance.
794
     * You may override this method if the default PDO needs to be adapted for certain DBMS.
795
     * @return PDO the pdo instance
796
     */
797 1979
    protected function createPdoInstance()
798
    {
799 1979
        $pdoClass = $this->pdoClass;
800 1979
        if ($pdoClass === null) {
0 ignored issues
show
introduced by
The condition $pdoClass === null is always false.
Loading history...
801 1979
            $pdoClass = 'PDO';
802 1979
            if ($this->_driverName !== null) {
803 239
                $driver = $this->_driverName;
804 1745
            } elseif (($pos = strpos($this->dsn, ':')) !== false) {
805 1745
                $driver = strtolower(substr($this->dsn, 0, $pos));
806
            }
807 1979
            if (isset($driver)) {
808 1979
                if ($driver === 'mssql' || $driver === 'dblib') {
809
                    $pdoClass = 'yii\db\mssql\PDO';
810 1979
                } 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...
811
                    $pdoClass = 'yii\db\mssql\SqlsrvPDO';
812
                }
813
            }
814
        }
815
816 1979
        $dsn = $this->dsn;
817 1979
        if (strncmp('sqlite:@', $dsn, 8) === 0) {
818 1
            $dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7));
819
        }
820
821 1979
        return new $pdoClass($dsn, $this->username, $this->password, $this->attributes);
822
    }
823
824
    /**
825
     * Initializes the DB connection.
826
     * This method is invoked right after the DB connection is established.
827
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
828
     * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
829
     * It then triggers an [[EVENT_AFTER_OPEN]] event.
830
     */
831 1979
    protected function initConnection()
832
    {
833 1979
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
834 1979
        if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
835
            if ($this->driverName !== 'sqlsrv') {
836
                $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
837
            }
838
        }
839 1979
        if (in_array($this->getDriverName(), ['mssql', 'dblib'], true)) {
840
            $this->pdo->exec('SET ANSI_NULL_DFLT_ON ON');
841
        }
842 1979
        if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'], true)) {
843
            $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
844
        }
845 1979
        $this->trigger(self::EVENT_AFTER_OPEN);
846 1979
    }
847
848
    /**
849
     * Creates a command for execution.
850
     * @param string $sql the SQL statement to be executed
851
     * @param array $params the parameters to be bound to the SQL statement
852
     * @return Command the DB command
853
     */
854 1596
    public function createCommand($sql = null, $params = [])
855
    {
856 1596
        $driver = $this->getDriverName();
857 1596
        $config = ['class' => 'yii\db\Command'];
858 1596
        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

858
        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...
859
            $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

859
            $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...
860 1596
        } elseif (isset($this->commandMap[$driver])) {
861 1596
            $config = !is_array($this->commandMap[$driver]) ? ['class' => $this->commandMap[$driver]] : $this->commandMap[$driver];
862
        }
863 1596
        $config['db'] = $this;
864 1596
        $config['sql'] = $sql;
865
        /** @var Command $command */
866 1596
        $command = Yii::createObject($config);
867 1596
        return $command->bindValues($params);
868
    }
869
870
    /**
871
     * Returns the currently active transaction.
872
     * @return Transaction|null the currently active transaction. Null if no active transaction.
873
     */
874 1565
    public function getTransaction()
875
    {
876 1565
        return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
877
    }
878
879
    /**
880
     * Starts a transaction.
881
     * @param string|null $isolationLevel The isolation level to use for this transaction.
882
     * See [[Transaction::begin()]] for details.
883
     * @return Transaction the transaction initiated
884
     */
885 39
    public function beginTransaction($isolationLevel = null)
886
    {
887 39
        $this->open();
888
889 39
        if (($transaction = $this->getTransaction()) === null) {
890 39
            $transaction = $this->_transaction = new Transaction(['db' => $this]);
891
        }
892 39
        $transaction->begin($isolationLevel);
893
894 39
        return $transaction;
895
    }
896
897
    /**
898
     * Executes callback provided in a transaction.
899
     *
900
     * @param callable $callback a valid PHP callback that performs the job. Accepts connection instance as parameter.
901
     * @param string|null $isolationLevel The isolation level to use for this transaction.
902
     * See [[Transaction::begin()]] for details.
903
     * @throws \Exception|\Throwable if there is any exception during query. In this case the transaction will be rolled back.
904
     * @return mixed result of callback function
905
     */
906 23
    public function transaction(callable $callback, $isolationLevel = null)
907
    {
908 23
        $transaction = $this->beginTransaction($isolationLevel);
909 23
        $level = $transaction->level;
910
911
        try {
912 23
            $result = call_user_func($callback, $this);
913 15
            if ($transaction->isActive && $transaction->level === $level) {
914 15
                $transaction->commit();
915
            }
916 8
        } catch (\Exception $e) {
917 8
            $this->rollbackTransactionOnLevel($transaction, $level);
918 8
            throw $e;
919
        } catch (\Throwable $e) {
920
            $this->rollbackTransactionOnLevel($transaction, $level);
921
            throw $e;
922
        }
923
924 15
        return $result;
925
    }
926
927
    /**
928
     * Rolls back given [[Transaction]] object if it's still active and level match.
929
     * In some cases rollback can fail, so this method is fail safe. Exception thrown
930
     * from rollback will be caught and just logged with [[\Yii::error()]].
931
     * @param Transaction $transaction Transaction object given from [[beginTransaction()]].
932
     * @param int $level Transaction level just after [[beginTransaction()]] call.
933
     */
934 8
    private function rollbackTransactionOnLevel($transaction, $level)
935
    {
936 8
        if ($transaction->isActive && $transaction->level === $level) {
937
            // https://github.com/yiisoft/yii2/pull/13347
938
            try {
939 8
                $transaction->rollBack();
940
            } catch (\Exception $e) {
941
                \Yii::error($e, __METHOD__);
942
                // hide this exception to be able to continue throwing original exception outside
943
            }
944
        }
945 8
    }
946
947
    /**
948
     * Returns the schema information for the database opened by this connection.
949
     * @return Schema the schema information for the database opened by this connection.
950
     * @throws NotSupportedException if there is no support for the current driver type
951
     */
952 2065
    public function getSchema()
953
    {
954 2065
        if ($this->_schema !== null) {
955 1687
            return $this->_schema;
956
        }
957
958 2010
        $driver = $this->getDriverName();
959 2010
        if (isset($this->schemaMap[$driver])) {
960 2010
            $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver];
961 2010
            $config['db'] = $this;
962
963 2010
            return $this->_schema = Yii::createObject($config);
964
        }
965
966
        throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
967
    }
968
969
    /**
970
     * Returns the query builder for the current DB connection.
971
     * @return QueryBuilder the query builder for the current DB connection.
972
     */
973 1100
    public function getQueryBuilder()
974
    {
975 1100
        return $this->getSchema()->getQueryBuilder();
976
    }
977
978
    /**
979
     * Can be used to set [[QueryBuilder]] configuration via Connection configuration array.
980
     *
981
     * @param array $value the [[QueryBuilder]] properties to be configured.
982
     * @since 2.0.14
983
     */
984
    public function setQueryBuilder($value)
985
    {
986
        Yii::configure($this->getQueryBuilder(), $value);
987
    }
988
989
    /**
990
     * Obtains the schema information for the named table.
991
     * @param string $name table name.
992
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
993
     * @return TableSchema table schema information. Null if the named table does not exist.
994
     */
995 242
    public function getTableSchema($name, $refresh = false)
996
    {
997 242
        return $this->getSchema()->getTableSchema($name, $refresh);
998
    }
999
1000
    /**
1001
     * Returns the ID of the last inserted row or sequence value.
1002
     * @param string $sequenceName name of the sequence object (required by some DBMS)
1003
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
1004
     * @see https://secure.php.net/manual/en/pdo.lastinsertid.php
1005
     */
1006 6
    public function getLastInsertID($sequenceName = '')
1007
    {
1008 6
        return $this->getSchema()->getLastInsertID($sequenceName);
1009
    }
1010
1011
    /**
1012
     * Quotes a string value for use in a query.
1013
     * Note that if the parameter is not a string, it will be returned without change.
1014
     * @param string $value string to be quoted
1015
     * @return string the properly quoted string
1016
     * @see https://secure.php.net/manual/en/pdo.quote.php
1017
     */
1018 1075
    public function quoteValue($value)
1019
    {
1020 1075
        return $this->getSchema()->quoteValue($value);
1021
    }
1022
1023
    /**
1024
     * Quotes a table name for use in a query.
1025
     * If the table name contains schema prefix, the prefix will also be properly quoted.
1026
     * If the table name is already quoted or contains special characters including '(', '[[' and '{{',
1027
     * then this method will do nothing.
1028
     * @param string $name table name
1029
     * @return string the properly quoted table name
1030
     */
1031 1361
    public function quoteTableName($name)
1032
    {
1033 1361
        if (isset($this->_quotedTableNames[$name])) {
1034 1002
            return $this->_quotedTableNames[$name];
1035
        }
1036 1295
        return $this->_quotedTableNames[$name] = $this->getSchema()->quoteTableName($name);
1037
    }
1038
1039
    /**
1040
     * Quotes a column name for use in a query.
1041
     * If the column name contains prefix, the prefix will also be properly quoted.
1042
     * If the column name is already quoted or contains special characters including '(', '[[' and '{{',
1043
     * then this method will do nothing.
1044
     * @param string $name column name
1045
     * @return string the properly quoted column name
1046
     */
1047 1416
    public function quoteColumnName($name)
1048
    {
1049 1416
        if (isset($this->_quotedColumnNames[$name])) {
1050 875
            return $this->_quotedColumnNames[$name];
1051
        }
1052 1361
        return $this->_quotedColumnNames[$name] = $this->getSchema()->quoteColumnName($name);
1053
    }
1054
1055
    /**
1056
     * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
1057
     * Tokens enclosed within double curly brackets are treated as table names, while
1058
     * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
1059
     * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
1060
     * with [[tablePrefix]].
1061
     * @param string $sql the SQL to be quoted
1062
     * @return string the quoted SQL
1063
     */
1064 1640
    public function quoteSql($sql)
1065
    {
1066 1640
        return preg_replace_callback(
1067 1640
            '/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
1068 1640
            function ($matches) {
1069 817
                if (isset($matches[3])) {
1070 601
                    return $this->quoteColumnName($matches[3]);
1071
                }
1072
1073 713
                return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
1074 1640
            },
1075 1640
            $sql
1076
        );
1077
    }
1078
1079
    /**
1080
     * Returns the name of the DB driver. Based on the the current [[dsn]], in case it was not set explicitly
1081
     * by an end user.
1082
     * @return string name of the DB driver
1083
     */
1084 2298
    public function getDriverName()
1085
    {
1086 2298
        if ($this->_driverName === null) {
1087 2229
            if (($pos = strpos($this->dsn, ':')) !== false) {
1088 2229
                $this->_driverName = strtolower(substr($this->dsn, 0, $pos));
1089
            } else {
1090
                $this->_driverName = strtolower($this->getReplicaPdo()->getAttribute(PDO::ATTR_DRIVER_NAME));
1091
            }
1092
        }
1093
1094 2298
        return $this->_driverName;
1095
    }
1096
1097
    /**
1098
     * Changes the current driver name.
1099
     * @param string $driverName name of the DB driver
1100
     */
1101
    public function setDriverName($driverName)
1102
    {
1103
        $this->_driverName = strtolower($driverName);
1104
    }
1105
1106
    /**
1107
     * Returns a server version as a string comparable by [[\version_compare()]].
1108
     * @return string server version as a string.
1109
     * @since 2.0.14
1110
     */
1111 411
    public function getServerVersion()
1112
    {
1113 411
        return $this->getSchema()->getServerVersion();
1114
    }
1115
1116
    /**
1117
     * Returns the PDO instance for the currently active replica connection.
1118
     * When [[enableReplicas]] is true, one of the replicas will be used for read queries, and its PDO instance
1119
     * will be returned by this method.
1120
     * @param bool $fallbackToPrimary whether to return the primary PDO if no replica connections are available.
1121
     * @return PDO|null the PDO instance for the currently active replica connection. `null` is returned if no
1122
     * replica connections are available and `$fallbackToPrimary` is false.
1123
     * @since 2.0.36
1124
     */
1125 1748
    public function getReplicaPdo($fallbackToPrimary = true)
1126
    {
1127 1748
        $db = $this->getReplica(false);
1128 1748
        if ($db === null) {
1129 1744
            return $fallbackToPrimary ? $this->getPrimaryPdo() : null;
1130
        }
1131
1132 5
        return $db->pdo;
1133
    }
1134
1135
    /**
1136
     * Returns the PDO instance for the currently active replica connection.
1137
     * When [[enableReplicas]] is true, one of the replicas will be used for read queries, and its PDO instance
1138
     * will be returned by this method.
1139
     * @param bool $fallbackToPrimary whether to return the primary PDO if no replica connections are available.
1140
     * @return PDO|null the PDO instance for the currently active replica connection. `null` is returned if no
1141
     * replica connections are available and `$fallbackToPrimary` is false.
1142
     * @deprecated since 2.0.36. Use [[getReplicaPdo()]] instead.
1143
     */
1144
    public function getSlavePdo($fallbackToPrimary = true)
1145
    {
1146
        return $this->getReplicaPdo($fallbackToPrimary);
1147
    }
1148
1149
    /**
1150
     * Returns the PDO instance for the currently active primary connection.
1151
     * This method will open the primary DB connection and then return [[pdo]].
1152
     * @return PDO the PDO instance for the currently active primary connection.
1153
     * @since 2.0.36
1154
     */
1155 1778
    public function getPrimaryPdo()
1156
    {
1157 1778
        $this->open();
1158 1778
        return $this->pdo;
1159
    }
1160
1161
    /**
1162
     * Returns the PDO instance for the currently active primary connection.
1163
     * This method will open the primary DB connection and then return [[pdo]].
1164
     * @return PDO the PDO instance for the currently active primary connection.
1165
     * @deprecated since 2.0.36. Use [[getPrimaryPdo()]] instead.
1166
     */
1167
    public function getMasterPdo()
1168
    {
1169
        return $this->getPrimaryPdo();
1170
    }
1171
1172
    /**
1173
     * Returns the currently active replica connection.
1174
     * If this method is called for the first time, it will try to open a replica connection when [[enableReplicas]]
1175
     * is true.
1176
     * @param bool $fallbackToPrimary whether to return the primary connection if no replica connections are
1177
     * available.
1178
     * @return Connection|null the currently active replica connection. `null` is returned if no replica connections
1179
     * are available and `$fallbackToPrimary` is false.
1180
     * @since 2.0.36
1181
     */
1182 1750
    public function getReplica($fallbackToPrimary = true)
1183
    {
1184 1750
        if (!$this->enableReplicas) {
1185 209
            return $fallbackToPrimary ? $this : null;
1186
        }
1187
1188 1651
        if ($this->_replica === false) {
1189 1651
            $this->_replica = $this->openFromPool($this->replicas, $this->replicaConfig);
1190
        }
1191
1192 1645
        return $this->_replica === null && $fallbackToPrimary ? $this : $this->_replica;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_replica =...$this : $this->_replica also could return the type true which is incompatible with the documented return type null|yii\db\Connection.
Loading history...
1193
    }
1194
1195
    /**
1196
     * Returns the currently active replica connection.
1197
     * If this method is called for the first time, it will try to open a replica connection when [[enableReplicas]]
1198
     * is true.
1199
     * @param bool $fallbackToPrimary whether to return the primary connection if no replica connections are
1200
     * available.
1201
     * @return Connection|null the currently active replica connection. `null` is returned if no replica connections
1202
     * are available and `$fallbackToPrimary` is false.
1203
     * @deprecated since 2.0.36. Use [[getReplica()]] instead.
1204
     */
1205
    public function getSlave($fallbackToPrimary = true)
1206
    {
1207
        return $this->getReplica($fallbackToPrimary);
1208
    }
1209
1210
1211
    /**
1212
     * Returns the currently active primary connection.
1213
     * If this method is called for the first time, it will try to open a primary connection.
1214
     * @return Connection|null the currently active primary connection. `null` is returned if no primary connection
1215
     * is available.
1216
     * @since 2.0.36
1217
     */
1218 11
    public function getPrimary()
1219
    {
1220 11
        if ($this->_primary === false) {
1221 11
            $this->_primary = $this->shufflePrimaries
1222 2
                ? $this->openFromPool($this->primaries, $this->primaryConfig)
1223 9
                : $this->openFromPoolSequentially($this->primaries, $this->primaryConfig);
1224
        }
1225
1226 11
        return $this->_primary;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->_primary also could return the type true which is incompatible with the documented return type null|yii\db\Connection.
Loading history...
1227
    }
1228
1229
    /**
1230
     * Returns the currently active primary connection.
1231
     * If this method is called for the first time, it will try to open a primary connection.
1232
     * @return Connection|null the currently active primary connection. `null` is returned if no primary connection
1233
     * is available.
1234
     * @since 2.0.11
1235
     * @deprecated since 2.0.36. Use [[getPrimary()]] instead.
1236
     */
1237
    public function getMaster()
1238
    {
1239
        return $this->getPrimary();
1240
    }
1241
1242
    /**
1243
     * Executes the provided callback by using the primary connection.
1244
     *
1245
     * This method is provided so that you can temporarily force using the primary connection to perform
1246
     * DB operations even if they are read queries. For example,
1247
     *
1248
     * ```php
1249
     * $result = $db->usePrimary(function ($db) {
1250
     *     return $db->createCommand('SELECT * FROM user LIMIT 1')->queryOne();
1251
     * });
1252
     * ```
1253
     *
1254
     * @param callable $callback a PHP callable to be executed by this method. Its signature is
1255
     * `function (Connection $db)`. Its return value will be returned by this method.
1256
     * @return mixed the return value of the callback
1257
     * @throws \Exception|\Throwable if there is any exception thrown from the callback
1258
     * @since 2.0.36
1259
     */
1260 100
    public function usePrimary(callable $callback)
1261
    {
1262 100
        if ($this->enableReplicas) {
1263 100
            $this->enableReplicas = false;
1264
            try {
1265 100
                $result = call_user_func($callback, $this);
1266 4
            } catch (\Exception $e) {
1267 4
                $this->enableReplicas = true;
1268 4
                throw $e;
1269
            } catch (\Throwable $e) {
1270
                $this->enableReplicas = true;
1271
                throw $e;
1272
            }
1273
            // TODO: use "finally" keyword when miminum required PHP version is >= 5.5
1274 96
            $this->enableReplicas = true;
1275
        } else {
1276
            $result = call_user_func($callback, $this);
1277
        }
1278
1279 96
        return $result;
1280
    }
1281
1282
    /**
1283
     * Executes the provided callback by using the primary connection.
1284
     *
1285
     * This method is provided so that you can temporarily force using the primary connection to perform
1286
     * DB operations even if they are read queries. For example,
1287
     *
1288
     * ```php
1289
     * $result = $db->usePrimary(function ($db) {
1290
     *     return $db->createCommand('SELECT * FROM user LIMIT 1')->queryOne();
1291
     * });
1292
     * ```
1293
     *
1294
     * @param callable $callback a PHP callable to be executed by this method. Its signature is
1295
     * `function (Connection $db)`. Its return value will be returned by this method.
1296
     * @return mixed the return value of the callback
1297
     * @throws \Exception|\Throwable if there is any exception thrown from the callback
1298
     * @deprecated since 2.0.36. Use [[usePrimary()]] instead.
1299
     */
1300
    public function useMaster(callable $callback)
1301
    {
1302
        return $this->usePrimary($callback);
1303
    }
1304
1305
    /**
1306
     * Opens the connection to a server in the pool.
1307
     *
1308
     * This method implements load balancing and failover among the given list of the servers.
1309
     * Connections will be tried in random order.
1310
     * For details about the failover behavior, see [[openFromPoolSequentially]].
1311
     *
1312
     * @param array $pool the list of connection configurations in the server pool
1313
     * @param array $sharedConfig the configuration common to those given in `$pool`.
1314
     * @return Connection the opened DB connection, or `null` if no server is available
1315
     * @throws InvalidConfigException if a configuration does not specify "dsn"
1316
     * @see openFromPoolSequentially
1317
     */
1318 1651
    protected function openFromPool(array $pool, array $sharedConfig)
1319
    {
1320 1651
        shuffle($pool);
1321 1651
        return $this->openFromPoolSequentially($pool, $sharedConfig);
1322
    }
1323
1324
    /**
1325
     * Opens the connection to a server in the pool.
1326
     *
1327
     * This method implements failover among the given list of servers.
1328
     * Connections will be tried in sequential order. The first successful connection will return.
1329
     *
1330
     * If [[serverStatusCache]] is configured, this method will cache information about
1331
     * unreachable servers and does not try to connect to these for the time configured in [[serverRetryInterval]].
1332
     * This helps to keep the application stable when some servers are unavailable. Avoiding
1333
     * connection attempts to unavailable servers saves time when the connection attempts fail due to timeout.
1334
     *
1335
     * If none of the servers are available the status cache is ignored and connection attempts are made to all
1336
     * servers (Since version 2.0.35). This is to avoid downtime when all servers are unavailable for a short time.
1337
     * After a successful connection attempt the server is marked as avaiable again.
1338
     *
1339
     * @param array $pool the list of connection configurations in the server pool
1340
     * @param array $sharedConfig the configuration common to those given in `$pool`.
1341
     * @return Connection the opened DB connection, or `null` if no server is available
1342
     * @throws InvalidConfigException if a configuration does not specify "dsn"
1343
     * @since 2.0.11
1344
     * @see openFromPool
1345
     * @see serverStatusCache
1346
     */
1347 1659
    protected function openFromPoolSequentially(array $pool, array $sharedConfig)
1348
    {
1349 1659
        if (empty($pool)) {
1350 1639
            return null;
1351
        }
1352
1353 21
        if (!isset($sharedConfig['class'])) {
1354 21
            $sharedConfig['class'] = get_class($this);
1355
        }
1356
1357 21
        $cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache;
1358
1359 21
        foreach ($pool as $i => $config) {
1360 21
            $pool[$i] = $config = array_merge($sharedConfig, $config);
1361 21
            if (empty($config['dsn'])) {
1362 6
                throw new InvalidConfigException('The "dsn" option must be specified.');
1363
            }
1364
1365 15
            $key = [__METHOD__, $config['dsn']];
1366 15
            if ($cache instanceof CacheInterface && $cache->get($key)) {
1367
                // should not try this dead server now
1368
                continue;
1369
            }
1370
1371
            /* @var $db Connection */
1372 15
            $db = Yii::createObject($config);
1373
1374
            try {
1375 15
                $db->open();
1376 15
                return $db;
1377 8
            } catch (\Exception $e) {
1378 8
                Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
1379 8
                if ($cache instanceof CacheInterface) {
1380
                    // mark this server as dead and only retry it after the specified interval
1381 4
                    $cache->set($key, 1, $this->serverRetryInterval);
1382
                }
1383
                // exclude server from retry below
1384 8
                unset($pool[$i]);
1385
            }
1386
        }
1387
1388 8
        if ($cache instanceof CacheInterface) {
1389
            // if server status cache is enabled and no server is available
1390
            // ignore the cache and try to connect anyway
1391
            // $pool now only contains servers we did not already try in the loop above
1392 4
            foreach ($pool as $config) {
1393
1394
                /* @var $db Connection */
1395
                $db = Yii::createObject($config);
1396
                try {
1397
                    $db->open();
1398
                } catch (\Exception $e) {
1399
                    Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
1400
                    continue;
1401
                }
1402
1403
                // mark this server as available again after successful connection
1404
                $cache->delete([__METHOD__, $config['dsn']]);
1405
1406
                return $db;
1407
            }
1408
        }
1409
1410 8
        return null;
1411
    }
1412
1413
    /**
1414
     * Close the connection before serializing.
1415
     * @return array
1416
     */
1417 19
    public function __sleep()
1418
    {
1419 19
        $fields = (array) $this;
1420
1421 19
        unset($fields['pdo']);
1422 19
        unset($fields["\000" . __CLASS__ . "\000" . '_primary']);
1423 19
        unset($fields["\000" . __CLASS__ . "\000" . '_replica']);
1424 19
        unset($fields["\000" . __CLASS__ . "\000" . '_transaction']);
1425 19
        unset($fields["\000" . __CLASS__ . "\000" . '_schema']);
1426
1427 19
        return array_keys($fields);
1428
    }
1429
1430
    /**
1431
     * Reset the connection after cloning.
1432
     */
1433 7
    public function __clone()
1434
    {
1435 7
        parent::__clone();
1436
1437 7
        $this->_primary = false;
1438 7
        $this->_replica = false;
1439 7
        $this->_schema = null;
1440 7
        $this->_transaction = null;
1441 7
        if (strncmp($this->dsn, 'sqlite::memory:', 15) !== 0) {
1442
            // reset PDO connection, unless its sqlite in-memory, which can only have one connection
1443 6
            $this->pdo = null;
1444
        }
1445 7
    }
1446
}
1447