Completed
Push — 2.1 ( 28b26f...4d9204 )
by Alexander
10:53
created

Connection::transaction()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 17
ccs 10
cts 10
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 2
crap 4
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. This property is
122
 * read-only.
123
 * @property Schema $schema The schema information for the database opened by this connection. This property
124
 * is read-only.
125
 * @property Connection $slave The currently active slave connection. `null` is returned if there is no slave
126
 * available and `$fallbackToMaster` is false. This property is read-only.
127
 * @property PDO $slavePdo The PDO instance for the currently active slave connection. `null` is returned if
128
 * no slave connection is available and `$fallbackToMaster` is false. This property is read-only.
129
 * @property Transaction $transaction The currently active transaction. Null if no active transaction. This
130
 * property is read-only.
131
 *
132
 * @author Qiang Xue <[email protected]>
133
 * @since 2.0
134
 */
135
class Connection extends Component
136
{
137
    /**
138
     * @event Event an event that is triggered after a DB connection is established
139
     */
140
    const EVENT_AFTER_OPEN = 'afterOpen';
141
    /**
142
     * @event Event an event that is triggered right before a top-level transaction is started
143
     */
144
    const EVENT_BEGIN_TRANSACTION = 'beginTransaction';
145
    /**
146
     * @event Event an event that is triggered right after a top-level transaction is committed
147
     */
148
    const EVENT_COMMIT_TRANSACTION = 'commitTransaction';
149
    /**
150
     * @event Event an event that is triggered right after a top-level transaction is rolled back
151
     */
152
    const EVENT_ROLLBACK_TRANSACTION = 'rollbackTransaction';
153
154
    /**
155
     * @var string the Data Source Name, or DSN, contains the information required to connect to the database.
156
     * Please refer to the [PHP manual](http://php.net/manual/en/pdo.construct.php) on
157
     * the format of the DSN string.
158
     *
159
     * For [SQLite](http://php.net/manual/en/ref.pdo-sqlite.connection.php) you may use a [path alias](guide:concept-aliases)
160
     * for specifying the database path, e.g. `sqlite:@app/data/db.sql`.
161
     *
162
     * @see charset
163
     */
164
    public $dsn;
165
    /**
166
     * @var string the username for establishing DB connection. Defaults to `null` meaning no username to use.
167
     */
168
    public $username;
169
    /**
170
     * @var string the password for establishing DB connection. Defaults to `null` meaning no password to use.
171
     */
172
    public $password;
173
    /**
174
     * @var array PDO attributes (name => value) that should be set when calling [[open()]]
175
     * to establish a DB connection. Please refer to the
176
     * [PHP manual](http://php.net/manual/en/pdo.setattribute.php) for
177
     * details about available attributes.
178
     */
179
    public $attributes;
180
    /**
181
     * @var PDO the PHP PDO instance associated with this DB connection.
182
     * This property is mainly managed by [[open()]] and [[close()]] methods.
183
     * When a DB connection is active, this property will represent a PDO instance;
184
     * otherwise, it will be null.
185
     * @see pdoClass
186
     */
187
    public $pdo;
188
    /**
189
     * @var bool whether to enable schema caching.
190
     * Note that in order to enable truly schema caching, a valid cache component as specified
191
     * by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true.
192
     * @see schemaCacheDuration
193
     * @see schemaCacheExclude
194
     * @see schemaCache
195
     */
196
    public $enableSchemaCache = false;
197
    /**
198
     * @var int number of seconds that table metadata can remain valid in cache.
199
     * Use 0 to indicate that the cached data will never expire.
200
     * @see enableSchemaCache
201
     */
202
    public $schemaCacheDuration = 3600;
203
    /**
204
     * @var array list of tables whose metadata should NOT be cached. Defaults to empty array.
205
     * The table names may contain schema prefix, if any. Do not quote the table names.
206
     * @see enableSchemaCache
207
     */
208
    public $schemaCacheExclude = [];
209
    /**
210
     * @var CacheInterface|string the cache object or the ID of the cache application component that
211
     * is used to cache the table metadata.
212
     * @see enableSchemaCache
213
     */
214
    public $schemaCache = 'cache';
215
    /**
216
     * @var bool whether to enable query caching.
217
     * Note that in order to enable query caching, a valid cache component as specified
218
     * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
219
     * Also, only the results of the queries enclosed within [[cache()]] will be cached.
220
     * @see queryCache
221
     * @see cache()
222
     * @see noCache()
223
     */
224
    public $enableQueryCache = true;
225
    /**
226
     * @var int the default number of seconds that query results can remain valid in cache.
227
     * Defaults to 3600, meaning 3600 seconds, or one hour. Use 0 to indicate that the cached data will never expire.
228
     * The value of this property will be used when [[cache()]] is called without a cache duration.
229
     * @see enableQueryCache
230
     * @see cache()
231
     */
232
    public $queryCacheDuration = 3600;
233
    /**
234
     * @var CacheInterface|string the cache object or the ID of the cache application component
235
     * that is used for query caching.
236
     * @see enableQueryCache
237
     */
238
    public $queryCache = 'cache';
239
    /**
240
     * @var string the charset used for database connection. The property is only used
241
     * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset
242
     * as configured by the database.
243
     *
244
     * For Oracle Database, the charset must be specified in the [[dsn]], for example for UTF-8 by appending `;charset=UTF-8`
245
     * to the DSN string.
246
     *
247
     * The same applies for if you're using GBK or BIG5 charset with MySQL, then it's highly recommended to
248
     * specify charset via [[dsn]] like `'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'`.
249
     */
250
    public $charset;
251
    /**
252
     * @var bool whether to turn on prepare emulation. Defaults to false, meaning PDO
253
     * will use the native prepare support if available. For some databases (such as MySQL),
254
     * this may need to be set true so that PDO can emulate the prepare support to bypass
255
     * the buggy native prepare support.
256
     * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed.
257
     */
258
    public $emulatePrepare;
259
    /**
260
     * @var string the common prefix or suffix for table names. If a table name is given
261
     * as `{{%TableName}}`, then the percentage character `%` will be replaced with this
262
     * property value. For example, `{{%post}}` becomes `{{tbl_post}}`.
263
     */
264
    public $tablePrefix = '';
265
    /**
266
     * @var array mapping between PDO driver names and [[Schema]] classes.
267
     * The keys of the array are PDO driver names while the values the corresponding
268
     * schema class name or configuration. Please refer to [[Yii::createObject()]] for
269
     * details on how to specify a configuration.
270
     *
271
     * This property is mainly used by [[getSchema()]] when fetching the database schema information.
272
     * You normally do not need to set this property unless you want to use your own
273
     * [[Schema]] class to support DBMS that is not supported by Yii.
274
     */
275
    public $schemaMap = [
276
        'pgsql' => pgsql\Schema::class, // PostgreSQL
277
        'mysqli' => mysql\Schema::class, // MySQL
278
        'mysql' => mysql\Schema::class, // MySQL
279
        'sqlite' => sqlite\Schema::class, // sqlite 3
280
        'sqlite2' => sqlite\Schema::class, // sqlite 2
281
        'sqlsrv' => mssql\Schema::class, // newer MSSQL driver on MS Windows hosts
282
        'oci' => oci\Schema::class, // Oracle driver
283
        'mssql' => mssql\Schema::class, // older MSSQL driver on MS Windows hosts
284
        'dblib' => mssql\Schema::class, // dblib drivers on GNU/Linux (and maybe other OSes) hosts
285
        'cubrid' => cubrid\Schema::class, // CUBRID
286
    ];
287
    /**
288
     * @var string Custom PDO wrapper class. If not set, it will use [[PDO]] or [[\yii\db\mssql\PDO]] when MSSQL is used.
289
     * @see pdo
290
     */
291
    public $pdoClass;
292
    /**
293
     * @var string the class used to create new database [[Command]] objects. If you want to extend the [[Command]] class,
294
     * you may configure this property to use your extended version of the class.
295
     * @see createCommand
296
     * @since 2.0.7
297
     */
298
    public $commandClass = Command::class;
299
    /**
300
     * @var bool whether to enable [savepoint](http://en.wikipedia.org/wiki/Savepoint).
301
     * Note that if the underlying DBMS does not support savepoint, setting this property to be true will have no effect.
302
     */
303
    public $enableSavepoint = true;
304
    /**
305
     * @var CacheInterface|string the cache object or the ID of the cache application component that is used to store
306
     * the health status of the DB servers specified in [[masters]] and [[slaves]].
307
     * This is used only when read/write splitting is enabled or [[masters]] is not empty.
308
     */
309
    public $serverStatusCache = 'cache';
310
    /**
311
     * @var int the retry interval in seconds for dead servers listed in [[masters]] and [[slaves]].
312
     * This is used together with [[serverStatusCache]].
313
     */
314
    public $serverRetryInterval = 600;
315
    /**
316
     * @var bool whether to enable read/write splitting by using [[slaves]] to read data.
317
     * Note that if [[slaves]] is empty, read/write splitting will NOT be enabled no matter what value this property takes.
318
     */
319
    public $enableSlaves = true;
320
    /**
321
     * @var array list of slave connection configurations. Each configuration is used to create a slave DB connection.
322
     * When [[enableSlaves]] is true, one of these configurations will be chosen and used to create a DB connection
323
     * for performing read queries only.
324
     * @see enableSlaves
325
     * @see slaveConfig
326
     */
327
    public $slaves = [];
328
    /**
329
     * @var array the configuration that should be merged with every slave configuration listed in [[slaves]].
330
     * For example,
331
     *
332
     * ```php
333
     * [
334
     *     'username' => 'slave',
335
     *     'password' => 'slave',
336
     *     'attributes' => [
337
     *         // use a smaller connection timeout
338
     *         PDO::ATTR_TIMEOUT => 10,
339
     *     ],
340
     * ]
341
     * ```
342
     */
343
    public $slaveConfig = [];
344
    /**
345
     * @var array list of master connection configurations. Each configuration is used to create a master DB connection.
346
     * When [[open()]] is called, one of these configurations will be chosen and used to create a DB connection
347
     * which will be used by this object.
348
     * Note that when this property is not empty, the connection setting (e.g. "dsn", "username") of this object will
349
     * be ignored.
350
     * @see masterConfig
351
     * @see shuffleMasters
352
     */
353
    public $masters = [];
354
    /**
355
     * @var array the configuration that should be merged with every master configuration listed in [[masters]].
356
     * For example,
357
     *
358
     * ```php
359
     * [
360
     *     'username' => 'master',
361
     *     'password' => 'master',
362
     *     'attributes' => [
363
     *         // use a smaller connection timeout
364
     *         PDO::ATTR_TIMEOUT => 10,
365
     *     ],
366
     * ]
367
     * ```
368
     */
369
    public $masterConfig = [];
370
    /**
371
     * @var bool whether to shuffle [[masters]] before getting one.
372
     * @since 2.0.11
373
     * @see masters
374
     */
375
    public $shuffleMasters = true;
376
    /**
377
     * @var bool whether to enable logging of database queries. Defaults to true.
378
     * You may want to disable this option in a production environment to gain performance
379
     * if you do not need the information being logged.
380
     * @since 2.0.12
381
     * @see enableProfiling
382
     */
383
    public $enableLogging = true;
384
    /**
385
     * @var bool whether to enable profiling of database queries. Defaults to true.
386
     * You may want to disable this option in a production environment to gain performance
387
     * if you do not need the information being logged.
388
     * @since 2.0.12
389
     * @see enableLogging
390
     */
391
    public $enableProfiling = true;
392
393
    /**
394
     * @var Transaction the currently active transaction
395
     */
396
    private $_transaction;
397
    /**
398
     * @var Schema the database schema
399
     */
400
    private $_schema;
401
    /**
402
     * @var string driver name
403
     */
404
    private $_driverName;
405
    /**
406
     * @var Connection|false the currently active master connection
407
     */
408
    private $_master = false;
409
    /**
410
     * @var Connection|false the currently active slave connection
411
     */
412
    private $_slave = false;
413
    /**
414
     * @var array query cache parameters for the [[cache()]] calls
415
     */
416
    private $_queryCacheInfo = [];
417
418
419
    /**
420
     * Returns a value indicating whether the DB connection is established.
421
     * @return bool whether the DB connection is established
422
     */
423 273
    public function getIsActive()
424
    {
425 273
        return $this->pdo !== null;
426
    }
427
428
    /**
429
     * Uses query cache for the queries performed with the callable.
430
     * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache),
431
     * queries performed within the callable will be cached and their results will be fetched from cache if available.
432
     * For example,
433
     *
434
     * ```php
435
     * // The customer will be fetched from cache if available.
436
     * // If not, the query will be made against DB and cached for use next time.
437
     * $customer = $db->cache(function (Connection $db) {
438
     *     return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
439
     * });
440
     * ```
441
     *
442
     * Note that query cache is only meaningful for queries that return results. For queries performed with
443
     * [[Command::execute()]], query cache will not be used.
444
     *
445
     * @param callable $callable a PHP callable that contains DB queries which will make use of query cache.
446
     * The signature of the callable is `function (Connection $db)`.
447
     * @param int $duration the number of seconds that query results can remain valid in the cache. If this is
448
     * not set, the value of [[queryCacheDuration]] will be used instead.
449
     * Use 0 to indicate that the cached data will never expire.
450
     * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
451
     * @return mixed the return result of the callable
452
     * @throws \Throwable if there is any exception during query
453
     * @see enableQueryCache
454
     * @see queryCache
455
     * @see noCache()
456
     */
457 3
    public function cache(callable $callable, $duration = null, $dependency = null)
458
    {
459 3
        $this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency];
460
        try {
461 3
            $result = call_user_func($callable, $this);
462 3
            array_pop($this->_queryCacheInfo);
463 3
            return $result;
464
        } catch (\Throwable $e) {
465
            array_pop($this->_queryCacheInfo);
466
            throw $e;
467
        }
468
    }
469
470
    /**
471
     * Disables query cache temporarily.
472
     * Queries performed within the callable will not use query cache at all. For example,
473
     *
474
     * ```php
475
     * $db->cache(function (Connection $db) {
476
     *
477
     *     // ... queries that use query cache ...
478
     *
479
     *     return $db->noCache(function (Connection $db) {
480
     *         // this query will not use query cache
481
     *         return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
482
     *     });
483
     * });
484
     * ```
485
     *
486
     * @param callable $callable a PHP callable that contains DB queries which should not use query cache.
487
     * The signature of the callable is `function (Connection $db)`.
488
     * @return mixed the return result of the callable
489
     * @throws \Throwable if there is any exception during query
490
     * @see enableQueryCache
491
     * @see queryCache
492
     * @see cache()
493
     */
494 19
    public function noCache(callable $callable)
495
    {
496 19
        $this->_queryCacheInfo[] = false;
497
        try {
498 19
            $result = call_user_func($callable, $this);
499 19
            array_pop($this->_queryCacheInfo);
500 19
            return $result;
501
        } catch (\Throwable $e) {
502
            array_pop($this->_queryCacheInfo);
503
            throw $e;
504
        }
505
    }
506
507
    /**
508
     * Returns the current query cache information.
509
     * This method is used internally by [[Command]].
510
     * @param int $duration the preferred caching duration. If null, it will be ignored.
511
     * @param \yii\caching\Dependency $dependency the preferred caching dependency. If null, it will be ignored.
512
     * @return array the current query cache information, or null if query cache is not enabled.
513
     * @internal
514
     */
515 1015
    public function getQueryCacheInfo($duration, $dependency)
516
    {
517 1015
        if (!$this->enableQueryCache) {
518 23
            return null;
519
        }
520
521 1014
        $info = end($this->_queryCacheInfo);
522 1014
        if (is_array($info)) {
523 3
            if ($duration === null) {
524 3
                $duration = $info[0];
525
            }
526 3
            if ($dependency === null) {
527 3
                $dependency = $info[1];
528
            }
529
        }
530
531 1014
        if ($duration === 0 || $duration > 0) {
532 3
            if (is_string($this->queryCache) && Yii::$app) {
533
                $cache = Yii::$app->get($this->queryCache, false);
534
            } else {
535 3
                $cache = $this->queryCache;
536
            }
537 3
            if ($cache instanceof CacheInterface) {
538 3
                return [$cache, $duration, $dependency];
539
            }
540
        }
541
542 1014
        return null;
543
    }
544
545
    /**
546
     * Establishes a DB connection.
547
     * It does nothing if a DB connection has already been established.
548
     * @throws Exception if connection fails
549
     */
550 1300
    public function open()
551
    {
552 1300
        if ($this->pdo !== null) {
553 1091
            return;
554
        }
555
556 1251
        if (!empty($this->masters)) {
557 1
            $db = $this->getMaster();
558 1
            if ($db !== null) {
559 1
                $this->pdo = $db->pdo;
560 1
                return;
561
            }
562
563
            throw new InvalidConfigException('None of the master DB servers is available.');
564
        }
565
566 1251
        if (empty($this->dsn)) {
567
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
568
        }
569 1251
        $token = 'Opening DB connection: ' . $this->dsn;
570
        try {
571 1251
            Yii::info($token, __METHOD__);
572 1251
            Yii::beginProfile($token, __METHOD__);
573 1251
            $this->pdo = $this->createPdoInstance();
574 1251
            $this->initConnection();
575 1251
            Yii::endProfile($token, __METHOD__);
576 4
        } catch (\PDOException $e) {
577 4
            Yii::endProfile($token, __METHOD__);
578 4
            throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
579
        }
580 1251
    }
581
582
    /**
583
     * Closes the currently active DB connection.
584
     * It does nothing if the connection is already closed.
585
     */
586 1527
    public function close()
587
    {
588 1527
        if ($this->_master) {
589
            if ($this->pdo === $this->_master->pdo) {
590
                $this->pdo = null;
591
            }
592
593
            $this->_master->close();
594
            $this->_master = false;
595
        }
596
597 1527
        if ($this->pdo !== null) {
598 1196
            Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__);
599 1196
            $this->pdo = null;
600 1196
            $this->_schema = null;
601 1196
            $this->_transaction = null;
602
        }
603
604 1527
        if ($this->_slave) {
605 4
            $this->_slave->close();
606 4
            $this->_slave = false;
607
        }
608 1527
    }
609
610
    /**
611
     * Creates the PDO instance.
612
     * This method is called by [[open]] to establish a DB connection.
613
     * The default implementation will create a PHP PDO instance.
614
     * You may override this method if the default PDO needs to be adapted for certain DBMS.
615
     * @return PDO the pdo instance
616
     */
617 1251
    protected function createPdoInstance()
618
    {
619 1251
        $pdoClass = $this->pdoClass;
620 1251
        if ($pdoClass === null) {
621 1251
            $pdoClass = 'PDO';
622 1251
            if ($this->_driverName !== null) {
623 99
                $driver = $this->_driverName;
624 1154
            } elseif (($pos = strpos($this->dsn, ':')) !== false) {
625 1154
                $driver = strtolower(substr($this->dsn, 0, $pos));
626
            }
627 1251
            if (isset($driver)) {
628 1251
                if ($driver === 'mssql' || $driver === 'dblib') {
629
                    $pdoClass = mssql\PDO::class;
630 1251
                } elseif ($driver === 'sqlsrv') {
631
                    $pdoClass = mssql\SqlsrvPDO::class;
632
                }
633
            }
634
        }
635
636 1251
        $dsn = $this->dsn;
637 1251
        if (strncmp('sqlite:@', $dsn, 8) === 0) {
638 1
            $dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7));
639
        }
640 1251
        return new $pdoClass($dsn, $this->username, $this->password, $this->attributes);
641
    }
642
643
    /**
644
     * Initializes the DB connection.
645
     * This method is invoked right after the DB connection is established.
646
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
647
     * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
648
     * It then triggers an [[EVENT_AFTER_OPEN]] event.
649
     */
650 1251
    protected function initConnection()
651
    {
652 1251
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
653 1251
        if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
654
            $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
655
        }
656 1251
        if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'], true)) {
657
            $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
658
        }
659 1251
        $this->trigger(self::EVENT_AFTER_OPEN);
660 1251
    }
661
662
    /**
663
     * Creates a command for execution.
664
     * @param string $sql the SQL statement to be executed
665
     * @param array $params the parameters to be bound to the SQL statement
666
     * @return Command the DB command
667
     */
668 1083
    public function createCommand($sql = null, $params = [])
669
    {
670
        /** @var Command $command */
671 1083
        $command = new $this->commandClass([
672 1083
            'db' => $this,
673 1083
            'sql' => $sql,
674
        ]);
675
676 1083
        return $command->bindValues($params);
677
    }
678
679
    /**
680
     * Returns the currently active transaction.
681
     * @return Transaction the currently active transaction. Null if no active transaction.
682
     */
683 1057
    public function getTransaction()
684
    {
685 1057
        return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
686
    }
687
688
    /**
689
     * Starts a transaction.
690
     * @param string|null $isolationLevel The isolation level to use for this transaction.
691
     * See [[Transaction::begin()]] for details.
692
     * @return Transaction the transaction initiated
693
     */
694 30
    public function beginTransaction($isolationLevel = null)
695
    {
696 30
        $this->open();
697
698 30
        if (($transaction = $this->getTransaction()) === null) {
699 30
            $transaction = $this->_transaction = new Transaction(['db' => $this]);
700
        }
701 30
        $transaction->begin($isolationLevel);
702
703 30
        return $transaction;
704
    }
705
706
    /**
707
     * Executes callback provided in a transaction.
708
     *
709
     * @param callable $callback a valid PHP callback that performs the job. Accepts connection instance as parameter.
710
     * @param string|null $isolationLevel The isolation level to use for this transaction.
711
     * See [[Transaction::begin()]] for details.
712
     * @throws \Throwable if there is any exception during query. In this case the transaction will be rolled back.
713
     * @return mixed result of callback function
714
     */
715 16
    public function transaction(callable $callback, $isolationLevel = null)
716
    {
717 16
        $transaction = $this->beginTransaction($isolationLevel);
718 16
        $level = $transaction->level;
719
720
        try {
721 16
            $result = call_user_func($callback, $this);
722 12
            if ($transaction->isActive && $transaction->level === $level) {
723 12
                $transaction->commit();
724
            }
725 4
        } catch (\Throwable $e) {
726 4
            $this->rollbackTransactionOnLevel($transaction, $level);
727 4
            throw $e;
728
        }
729
730 12
        return $result;
731
    }
732
733
    /**
734
     * Rolls back given [[Transaction]] object if it's still active and level match.
735
     * In some cases rollback can fail, so this method is fail safe. Exception thrown
736
     * from rollback will be caught and just logged with [[\Yii::error()]].
737
     * @param Transaction $transaction Transaction object given from [[beginTransaction()]].
738
     * @param int $level Transaction level just after [[beginTransaction()]] call.
739
     */
740 4
    private function rollbackTransactionOnLevel($transaction, $level)
741
    {
742 4
        if ($transaction->isActive && $transaction->level === $level) {
743
            // https://github.com/yiisoft/yii2/pull/13347
744
            try {
745 4
                $transaction->rollBack();
746
            } catch (\Exception $e) {
747
                \Yii::error($e, __METHOD__);
748
                // hide this exception to be able to continue throwing original exception outside
749
            }
750
        }
751 4
    }
752
753
    /**
754
     * Returns the schema information for the database opened by this connection.
755
     * @return Schema the schema information for the database opened by this connection.
756
     * @throws NotSupportedException if there is no support for the current driver type
757
     */
758 1386
    public function getSchema()
759
    {
760 1386
        if ($this->_schema !== null) {
761 1140
            return $this->_schema;
762
        }
763
764 1329
        $driver = $this->getDriverName();
765 1329
        if (isset($this->schemaMap[$driver])) {
766 1329
            $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver];
767 1329
            $config['db'] = $this;
768
769 1329
            return $this->_schema = Yii::createObject($config);
770
        }
771
772
        throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
773
    }
774
775
    /**
776
     * Returns the query builder for the current DB connection.
777
     * @return QueryBuilder the query builder for the current DB connection.
778
     */
779 805
    public function getQueryBuilder()
780
    {
781 805
        return $this->getSchema()->getQueryBuilder();
782
    }
783
784
    /**
785
     * Obtains the schema information for the named table.
786
     * @param string $name table name.
787
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
788
     * @return TableSchema table schema information. Null if the named table does not exist.
789
     */
790 111
    public function getTableSchema($name, $refresh = false)
791
    {
792 111
        return $this->getSchema()->getTableSchema($name, $refresh);
793
    }
794
795
    /**
796
     * Returns the ID of the last inserted row or sequence value.
797
     * @param string $sequenceName name of the sequence object (required by some DBMS)
798
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
799
     * @see http://php.net/manual/en/pdo.lastinsertid.php
800
     */
801
    public function getLastInsertID($sequenceName = '')
802
    {
803
        return $this->getSchema()->getLastInsertID($sequenceName);
804
    }
805
806
    /**
807
     * Quotes a string value for use in a query.
808
     * Note that if the parameter is not a string, it will be returned without change.
809
     * @param string $value string to be quoted
810
     * @return string the properly quoted string
811
     * @see http://php.net/manual/en/pdo.quote.php
812
     */
813 767
    public function quoteValue($value)
814
    {
815 767
        return $this->getSchema()->quoteValue($value);
816
    }
817
818
    /**
819
     * Quotes a table name for use in a query.
820
     * If the table name contains schema prefix, the prefix will also be properly quoted.
821
     * If the table name is already quoted or contains special characters including '(', '[[' and '{{',
822
     * then this method will do nothing.
823
     * @param string $name table name
824
     * @return string the properly quoted table name
825
     */
826 911
    public function quoteTableName($name)
827
    {
828 911
        return $this->getSchema()->quoteTableName($name);
829
    }
830
831
    /**
832
     * Quotes a column name for use in a query.
833
     * If the column name contains prefix, the prefix will also be properly quoted.
834
     * If the column name is already quoted or contains special characters including '(', '[[' and '{{',
835
     * then this method will do nothing.
836
     * @param string $name column name
837
     * @return string the properly quoted column name
838
     */
839 937
    public function quoteColumnName($name)
840
    {
841 937
        return $this->getSchema()->quoteColumnName($name);
842
    }
843
844
    /**
845
     * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
846
     * Tokens enclosed within double curly brackets are treated as table names, while
847
     * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
848
     * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
849
     * with [[tablePrefix]].
850
     * @param string $sql the SQL to be quoted
851
     * @return string the quoted SQL
852
     */
853 1126
    public function quoteSql($sql)
854
    {
855 1126
        return preg_replace_callback(
856 1126
            '/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
857 1126
            function ($matches) {
858 536
                if (isset($matches[3])) {
859 394
                    return $this->quoteColumnName($matches[3]);
860
                }
861
862 495
                return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
863 1126
            },
864 1126
            $sql
865
        );
866
    }
867
868
    /**
869
     * Returns the name of the DB driver. Based on the the current [[dsn]], in case it was not set explicitly
870
     * by an end user.
871
     * @return string name of the DB driver
872
     */
873 1339
    public function getDriverName()
874
    {
875 1339
        if ($this->_driverName === null) {
876 1319
            if (($pos = strpos($this->dsn, ':')) !== false) {
877 1319
                $this->_driverName = strtolower(substr($this->dsn, 0, $pos));
878
            } else {
879
                $this->_driverName = strtolower($this->getSlavePdo()->getAttribute(PDO::ATTR_DRIVER_NAME));
880
            }
881
        }
882 1339
        return $this->_driverName;
883
    }
884
885
    /**
886
     * Changes the current driver name.
887
     * @param string $driverName name of the DB driver
888
     */
889
    public function setDriverName($driverName)
890
    {
891
        $this->_driverName = strtolower($driverName);
892
    }
893
894
    /**
895
     * Returns the PDO instance for the currently active slave connection.
896
     * When [[enableSlaves]] is true, one of the slaves will be used for read queries, and its PDO instance
897
     * will be returned by this method.
898
     * @param bool $fallbackToMaster whether to return a master PDO in case none of the slave connections is available.
899
     * @return PDO the PDO instance for the currently active slave connection. `null` is returned if no slave connection
900
     * is available and `$fallbackToMaster` is false.
901
     */
902 1073
    public function getSlavePdo($fallbackToMaster = true)
903
    {
904 1073
        $db = $this->getSlave(false);
905 1073
        if ($db === null) {
906 1069
            return $fallbackToMaster ? $this->getMasterPdo() : null;
907
        }
908
909 5
        return $db->pdo;
910
    }
911
912
    /**
913
     * Returns the PDO instance for the currently active master connection.
914
     * This method will open the master DB connection and then return [[pdo]].
915
     * @return PDO the PDO instance for the currently active master connection.
916
     */
917 1098
    public function getMasterPdo()
918
    {
919 1098
        $this->open();
920 1098
        return $this->pdo;
921
    }
922
923
    /**
924
     * Returns the currently active slave connection.
925
     * If this method is called for the first time, it will try to open a slave connection when [[enableSlaves]] is true.
926
     * @param bool $fallbackToMaster whether to return a master connection in case there is no slave connection available.
927
     * @return Connection the currently active slave connection. `null` is returned if there is no slave available and
928
     * `$fallbackToMaster` is false.
929
     */
930 1075
    public function getSlave($fallbackToMaster = true)
931
    {
932 1075
        if (!$this->enableSlaves) {
933 83
            return $fallbackToMaster ? $this : null;
934
        }
935
936 1010
        if ($this->_slave === false) {
937 1010
            $this->_slave = $this->openFromPool($this->slaves, $this->slaveConfig);
938
        }
939
940 1010
        return $this->_slave === null && $fallbackToMaster ? $this : $this->_slave;
941
    }
942
943
    /**
944
     * Returns the currently active master connection.
945
     * If this method is called for the first time, it will try to open a master connection.
946
     * @return Connection the currently active master connection. `null` is returned if there is no master available.
947
     * @since 2.0.11
948
     */
949 3
    public function getMaster()
950
    {
951 3
        if ($this->_master === false) {
952 3
            $this->_master = ($this->shuffleMasters)
953 2
                ? $this->openFromPool($this->masters, $this->masterConfig)
954 1
                : $this->openFromPoolSequentially($this->masters, $this->masterConfig);
955
        }
956
957 3
        return $this->_master;
958
    }
959
960
    /**
961
     * Executes the provided callback by using the master connection.
962
     *
963
     * This method is provided so that you can temporarily force using the master connection to perform
964
     * DB operations even if they are read queries. For example,
965
     *
966
     * ```php
967
     * $result = $db->useMaster(function ($db) {
968
     *     return $db->createCommand('SELECT * FROM user LIMIT 1')->queryOne();
969
     * });
970
     * ```
971
     *
972
     * @param callable $callback a PHP callable to be executed by this method. Its signature is
973
     * `function (Connection $db)`. Its return value will be returned by this method.
974
     * @return mixed the return value of the callback
975
     * @throws \Throwable if there is any exception thrown from the callback
976
     */
977 7
    public function useMaster(callable $callback)
978
    {
979 7
        if ($this->enableSlaves) {
980 7
            $this->enableSlaves = false;
981
            try {
982 7
                $result = call_user_func($callback, $this);
983 1
            } catch (\Throwable $e) {
984 1
                $this->enableSlaves = true;
985 1
                throw $e;
986
            }
987
            // TODO: use "finally" keyword when miminum required PHP version is >= 5.5
988 6
            $this->enableSlaves = true;
989
        } else {
990
            $result = call_user_func($callback, $this);
991
        }
992
993 6
        return $result;
994
    }
995
996
    /**
997
     * Opens the connection to a server in the pool.
998
     * This method implements the load balancing among the given list of the servers.
999
     * Connections will be tried in random order.
1000
     * @param array $pool the list of connection configurations in the server pool
1001
     * @param array $sharedConfig the configuration common to those given in `$pool`.
1002
     * @return Connection the opened DB connection, or `null` if no server is available
1003
     * @throws InvalidConfigException if a configuration does not specify "dsn"
1004
     */
1005 1010
    protected function openFromPool(array $pool, array $sharedConfig)
1006
    {
1007 1010
        shuffle($pool);
1008 1010
        return $this->openFromPoolSequentially($pool, $sharedConfig);
1009
    }
1010
1011
    /**
1012
     * Opens the connection to a server in the pool.
1013
     * This method implements the load balancing among the given list of the servers.
1014
     * Connections will be tried in sequential order.
1015
     * @param array $pool the list of connection configurations in the server pool
1016
     * @param array $sharedConfig the configuration common to those given in `$pool`.
1017
     * @return Connection the opened DB connection, or `null` if no server is available
1018
     * @throws InvalidConfigException if a configuration does not specify "dsn"
1019
     * @since 2.0.11
1020
     */
1021 1010
    protected function openFromPoolSequentially(array $pool, array $sharedConfig)
1022
    {
1023 1010
        if (empty($pool)) {
1024 1004
            return null;
1025
        }
1026
1027 7
        if (!isset($sharedConfig['class'])) {
1028 7
            $sharedConfig['class'] = get_class($this);
1029
        }
1030
1031 7
        $cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache;
1032
1033 7
        foreach ($pool as $config) {
1034 7
            $config = array_merge($sharedConfig, $config);
1035 7
            if (empty($config['dsn'])) {
1036
                throw new InvalidConfigException('The "dsn" option must be specified.');
1037
            }
1038
1039 7
            $key = [__METHOD__, $config['dsn']];
1040 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...
1041
                // should not try this dead server now
1042
                continue;
1043
            }
1044
1045
            /* @var $db Connection */
1046 7
            $db = Yii::createObject($config);
1047
1048
            try {
1049 7
                $db->open();
1050 7
                return $db;
1051
            } catch (\Exception $e) {
1052
                Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
1053
                if ($cache instanceof CacheInterface) {
1054
                    // mark this server as dead and only retry it after the specified interval
1055
                    $cache->set($key, 1, $this->serverRetryInterval);
1056
                }
1057
            }
1058
        }
1059
1060
        return null;
1061
    }
1062
1063
    /**
1064
     * Close the connection before serializing.
1065
     * @return array
1066
     */
1067 14
    public function __sleep()
1068
    {
1069 14
        $this->close();
1070 14
        return array_keys((array) $this);
1071
    }
1072
1073
    /**
1074
     * Reset the connection after cloning.
1075
     */
1076 7
    public function __clone()
1077
    {
1078 7
        parent::__clone();
1079
1080 7
        $this->_master = false;
1081 7
        $this->_slave = false;
1082 7
        $this->_schema = null;
1083 7
        $this->_transaction = null;
1084 7
        if (strncmp($this->dsn, 'sqlite::memory:', 15) !== 0) {
1085
            // reset PDO connection, unless its sqlite in-memory, which can only have one connection
1086 6
            $this->pdo = null;
1087
        }
1088 7
    }
1089
}
1090