Completed
Push — 2.1 ( 9ef1c4...d51179 )
by
unknown
11:15
created

Connection::getMasterPdo()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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

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...
713
            $config['class'] = $this->commandClass;
0 ignored issues
show
Deprecated Code introduced by
The property yii\db\Connection::$commandClass has been deprecated with message: since 2.0.14. Use [[$commandMap]] for precise configuration.

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

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1108
                // should not try this dead server now
1109
                continue;
1110
            }
1111
1112
            /* @var $db Connection */
1113 7
            $db = Yii::createObject($config);
1114
1115
            try {
1116 7
                $db->open();
1117 7
                return $db;
1118
            } catch (\Exception $e) {
1119
                Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
1120
                if ($cache instanceof CacheInterface) {
1121
                    // mark this server as dead and only retry it after the specified interval
1122
                    $cache->set($key, 1, $this->serverRetryInterval);
1123
                }
1124
            }
1125
        }
1126
1127
        return null;
1128
    }
1129
1130
    /**
1131
     * Close the connection before serializing.
1132
     * @return array
1133
     */
1134 17
    public function __sleep()
1135
    {
1136 17
        $fields = (array) $this;
1137
1138 17
        unset($fields['pdo']);
1139 17
        unset($fields["\000" . __CLASS__ . "\000" . '_master']);
1140 17
        unset($fields["\000" . __CLASS__ . "\000" . '_slave']);
1141 17
        unset($fields["\000" . __CLASS__ . "\000" . '_transaction']);
1142 17
        unset($fields["\000" . __CLASS__ . "\000" . '_schema']);
1143
1144 17
        return array_keys($fields);
1145
    }
1146
1147
    /**
1148
     * Reset the connection after cloning.
1149
     */
1150 7
    public function __clone()
1151
    {
1152 7
        parent::__clone();
1153
1154 7
        $this->_master = false;
1155 7
        $this->_slave = false;
1156 7
        $this->_schema = null;
1157 7
        $this->_transaction = null;
1158 7
        if (strncmp($this->dsn, 'sqlite::memory:', 15) !== 0) {
1159
            // reset PDO connection, unless its sqlite in-memory, which can only have one connection
1160 6
            $this->pdo = null;
1161
        }
1162 7
    }
1163
}
1164