Completed
Push — 2.1 ( 892f1a...290621 )
by Carsten
17:25 queued 13:07
created

Connection::beginTransaction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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