Completed
Push — cache-closure ( 7d9053...380edd )
by Dmitry
35:54
created

Connection::transaction()   C

Complexity

Conditions 9
Paths 10

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 10.7365

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 13
cts 18
cp 0.7221
rs 5.3563
c 0
b 0
f 0
cc 9
eloc 16
nc 10
nop 2
crap 10.7365
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\Cache;
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',
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 PDO $masterPdo The PDO instance for the currently active master connection. This property is
118
 * read-only.
119
 * @property QueryBuilder $queryBuilder The query builder for the current DB connection. This property is
120
 * read-only.
121
 * @property Schema $schema The schema information for the database opened by this connection. This property
122
 * is read-only.
123
 * @property Connection $slave The currently active slave connection. Null is returned if there is slave
124
 * available and `$fallbackToMaster` is false. This property is read-only.
125
 * @property PDO $slavePdo The PDO instance for the currently active slave connection. Null is returned if no
126
 * slave connection is available and `$fallbackToMaster` is false. This property is read-only.
127
 * @property Transaction $transaction The currently active transaction. Null if no active transaction. This
128
 * property is read-only.
129
 *
130
 * @author Qiang Xue <[email protected]>
131
 * @since 2.0
132
 */
133
class Connection extends Component
134
{
135
    /**
136
     * @event Event an event that is triggered after a DB connection is established
137
     */
138
    const EVENT_AFTER_OPEN = 'afterOpen';
139
    /**
140
     * @event Event an event that is triggered right before a top-level transaction is started
141
     */
142
    const EVENT_BEGIN_TRANSACTION = 'beginTransaction';
143
    /**
144
     * @event Event an event that is triggered right after a top-level transaction is committed
145
     */
146
    const EVENT_COMMIT_TRANSACTION = 'commitTransaction';
147
    /**
148
     * @event Event an event that is triggered right after a top-level transaction is rolled back
149
     */
150
    const EVENT_ROLLBACK_TRANSACTION = 'rollbackTransaction';
151
152
    /**
153
     * @var string the Data Source Name, or DSN, contains the information required to connect to the database.
154
     * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on
155
     * the format of the DSN string.
156
     *
157
     * For [SQLite](http://php.net/manual/en/ref.pdo-sqlite.connection.php) you may use a path alias
158
     * for specifying the database path, e.g. `sqlite:@app/data/db.sql`.
159
     *
160
     * @see charset
161
     */
162
    public $dsn;
163
    /**
164
     * @var string the username for establishing DB connection. Defaults to `null` meaning no username to use.
165
     */
166
    public $username;
167
    /**
168
     * @var string the password for establishing DB connection. Defaults to `null` meaning no password to use.
169
     */
170
    public $password;
171
    /**
172
     * @var array PDO attributes (name => value) that should be set when calling [[open()]]
173
     * to establish a DB connection. Please refer to the
174
     * [PHP manual](http://www.php.net/manual/en/function.PDO-setAttribute.php) for
175
     * details about available attributes.
176
     */
177
    public $attributes;
178
    /**
179
     * @var PDO the PHP PDO instance associated with this DB connection.
180
     * This property is mainly managed by [[open()]] and [[close()]] methods.
181
     * When a DB connection is active, this property will represent a PDO instance;
182
     * otherwise, it will be null.
183
     * @see pdoClass
184
     */
185
    public $pdo;
186
    /**
187
     * @var bool whether to enable schema caching.
188
     * Note that in order to enable truly schema caching, a valid cache component as specified
189
     * by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true.
190
     * @see schemaCacheDuration
191
     * @see schemaCacheExclude
192
     * @see schemaCache
193
     */
194
    public $enableSchemaCache = false;
195
    /**
196
     * @var int number of seconds that table metadata can remain valid in cache.
197
     * Use 0 to indicate that the cached data will never expire.
198
     * @see enableSchemaCache
199
     */
200
    public $schemaCacheDuration = 3600;
201
    /**
202
     * @var array list of tables whose metadata should NOT be cached. Defaults to empty array.
203
     * The table names may contain schema prefix, if any. Do not quote the table names.
204
     * @see enableSchemaCache
205
     */
206
    public $schemaCacheExclude = [];
207
    /**
208
     * @var Cache|string the cache object or the ID of the cache application component that
209
     * is used to cache the table metadata.
210
     * @see enableSchemaCache
211
     */
212
    public $schemaCache = 'cache';
213
    /**
214
     * @var bool whether to enable query caching.
215
     * Note that in order to enable query caching, a valid cache component as specified
216
     * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
217
     * Also, only the results of the queries enclosed within [[cache()]] will be cached.
218
     * @see queryCache
219
     * @see cache()
220
     * @see noCache()
221
     */
222
    public $enableQueryCache = true;
223
    /**
224
     * @var int the default number of seconds that query results can remain valid in cache.
225
     * Defaults to 3600, meaning 3600 seconds, or one hour. Use 0 to indicate that the cached data will never expire.
226
     * The value of this property will be used when [[cache()]] is called without a cache duration.
227
     * @see enableQueryCache
228
     * @see cache()
229
     */
230
    public $queryCacheDuration = 3600;
231
    /**
232
     * @var Cache|string the cache object or the ID of the cache application component
233
     * that is used for query caching.
234
     * @see enableQueryCache
235
     */
236
    public $queryCache = 'cache';
237
    /**
238
     * @var string the charset used for database connection. The property is only used
239
     * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset
240
     * as configured by the database.
241
     *
242
     * For Oracle Database, the charset must be specified in the [[dsn]], for example for UTF-8 by appending `;charset=UTF-8`
243
     * to the DSN string.
244
     *
245
     * The same applies for if you're using GBK or BIG5 charset with MySQL, then it's highly recommended to
246
     * specify charset via [[dsn]] like `'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'`.
247
     */
248
    public $charset;
249
    /**
250
     * @var bool whether to turn on prepare emulation. Defaults to false, meaning PDO
251
     * will use the native prepare support if available. For some databases (such as MySQL),
252
     * this may need to be set true so that PDO can emulate the prepare support to bypass
253
     * the buggy native prepare support.
254
     * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed.
255
     */
256
    public $emulatePrepare;
257
    /**
258
     * @var string the common prefix or suffix for table names. If a table name is given
259
     * as `{{%TableName}}`, then the percentage character `%` will be replaced with this
260
     * property value. For example, `{{%post}}` becomes `{{tbl_post}}`.
261
     */
262
    public $tablePrefix = '';
263
    /**
264
     * @var array mapping between PDO driver names and [[Schema]] classes.
265
     * The keys of the array are PDO driver names while the values the corresponding
266
     * schema class name or configuration. Please refer to [[Yii::createObject()]] for
267
     * details on how to specify a configuration.
268
     *
269
     * This property is mainly used by [[getSchema()]] when fetching the database schema information.
270
     * You normally do not need to set this property unless you want to use your own
271
     * [[Schema]] class to support DBMS that is not supported by Yii.
272
     */
273
    public $schemaMap = [
274
        'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL
275
        'mysqli' => 'yii\db\mysql\Schema', // MySQL
276
        'mysql' => 'yii\db\mysql\Schema', // MySQL
277
        'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
278
        'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
279
        'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts
280
        'oci' => 'yii\db\oci\Schema', // Oracle driver
281
        'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts
282
        'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts
283
        'cubrid' => 'yii\db\cubrid\Schema', // CUBRID
284
    ];
285
    /**
286
     * @var string Custom PDO wrapper class. If not set, it will use [[PDO]] or [[\yii\db\mssql\PDO]] when MSSQL is used.
287
     * @see pdo
288
     */
289
    public $pdoClass;
290
    /**
291
     * @var string the class used to create new database [[Command]] objects. If you want to extend the [[Command]] class,
292
     * you may configure this property to use your extended version of the class.
293
     * @see createCommand
294
     * @since 2.0.7
295
     */
296
    public $commandClass = 'yii\db\Command';
297
    /**
298
     * @var bool whether to enable [savepoint](http://en.wikipedia.org/wiki/Savepoint).
299
     * Note that if the underlying DBMS does not support savepoint, setting this property to be true will have no effect.
300
     */
301
    public $enableSavepoint = true;
302
    /**
303
     * @var Cache|string the cache object or the ID of the cache application component that is used to store
304
     * the health status of the DB servers specified in [[masters]] and [[slaves]].
305
     * This is used only when read/write splitting is enabled or [[masters]] is not empty.
306
     */
307
    public $serverStatusCache = 'cache';
308
    /**
309
     * @var int the retry interval in seconds for dead servers listed in [[masters]] and [[slaves]].
310
     * This is used together with [[serverStatusCache]].
311
     */
312
    public $serverRetryInterval = 600;
313
    /**
314
     * @var bool whether to enable read/write splitting by using [[slaves]] to read data.
315
     * Note that if [[slaves]] is empty, read/write splitting will NOT be enabled no matter what value this property takes.
316
     */
317
    public $enableSlaves = true;
318
    /**
319
     * @var array list of slave connection configurations. Each configuration is used to create a slave DB connection.
320
     * When [[enableSlaves]] is true, one of these configurations will be chosen and used to create a DB connection
321
     * for performing read queries only.
322
     * @see enableSlaves
323
     * @see slaveConfig
324
     */
325
    public $slaves = [];
326
    /**
327
     * @var array the configuration that should be merged with every slave configuration listed in [[slaves]].
328
     * For example,
329
     *
330
     * ```php
331
     * [
332
     *     'username' => 'slave',
333
     *     'password' => 'slave',
334
     *     'attributes' => [
335
     *         // use a smaller connection timeout
336
     *         PDO::ATTR_TIMEOUT => 10,
337
     *     ],
338
     * ]
339
     * ```
340
     */
341
    public $slaveConfig = [];
342
    /**
343
     * @var array list of master connection configurations. Each configuration is used to create a master DB connection.
344
     * When [[open()]] is called, one of these configurations will be chosen and used to create a DB connection
345
     * which will be used by this object.
346
     * Note that when this property is not empty, the connection setting (e.g. "dsn", "username") of this object will
347
     * be ignored.
348
     * @see masterConfig
349
     */
350
    public $masters = [];
351
    /**
352
     * @var array the configuration that should be merged with every master configuration listed in [[masters]].
353
     * For example,
354
     *
355
     * ```php
356
     * [
357
     *     'username' => 'master',
358
     *     'password' => 'master',
359
     *     'attributes' => [
360
     *         // use a smaller connection timeout
361
     *         PDO::ATTR_TIMEOUT => 10,
362
     *     ],
363
     * ]
364
     * ```
365
     */
366
    public $masterConfig = [];
367
368
    /**
369
     * @var Transaction the currently active transaction
370
     */
371
    private $_transaction;
372
    /**
373
     * @var Schema the database schema
374
     */
375
    private $_schema;
376
    /**
377
     * @var string driver name
378
     */
379
    private $_driverName;
380
    /**
381
     * @var Connection the currently active slave connection
382
     */
383
    private $_slave = false;
384
    /**
385
     * @var array query cache parameters for the [[cache()]] calls
386
     */
387
    private $_queryCacheInfo = [];
388
389
390
    /**
391
     * Returns a value indicating whether the DB connection is established.
392
     * @return bool whether the DB connection is established
393
     */
394 69
    public function getIsActive()
395
    {
396 69
        return $this->pdo !== null;
397
    }
398
399
    /**
400
     * Uses query cache for the queries performed with the callable.
401
     * When query caching is enabled ([[enableQueryCache]] is true and [[queryCache]] refers to a valid cache),
402
     * queries performed within the callable will be cached and their results will be fetched from cache if available.
403
     * For example,
404
     *
405
     * ```php
406
     * // The customer will be fetched from cache if available.
407
     * // If not, the query will be made against DB and cached for use next time.
408
     * $customer = $db->cache(function (Connection $db) {
409
     *     return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
410
     * });
411
     * ```
412
     *
413
     * Note that query cache is only meaningful for queries that return results. For queries performed with
414
     * [[Command::execute()]], query cache will not be used.
415
     *
416
     * @param callable $callable a PHP callable that contains DB queries which will make use of query cache.
417
     * The signature of the callable is `function (Connection $db)`.
418
     * @param int $duration the number of seconds that query results can remain valid in the cache. If this is
419
     * not set, the value of [[queryCacheDuration]] will be used instead.
420
     * Use 0 to indicate that the cached data will never expire.
421
     * @param \yii\caching\Dependency $dependency the cache dependency associated with the cached query results.
422
     * @return mixed the return result of the callable
423
     * @throws \Exception|\Throwable if there is any exception during query
424
     * @see enableQueryCache
425
     * @see queryCache
426
     * @see noCache()
427
     */
428 3
    public function cache(callable $callable, $duration = null, $dependency = null)
429
    {
430 3
        $this->_queryCacheInfo[] = [$duration === null ? $this->queryCacheDuration : $duration, $dependency];
431
        try {
432 3
            $result = call_user_func($callable, $this);
433 3
            array_pop($this->_queryCacheInfo);
434 3
            return $result;
435
        } catch (\Exception $e) {
436
            array_pop($this->_queryCacheInfo);
437
            throw $e;
438
        } catch (\Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
439
            array_pop($this->_queryCacheInfo);
440
            throw $e;
441
        }
442
    }
443
444
    /**
445
     * Disables query cache temporarily.
446
     * Queries performed within the callable will not use query cache at all. For example,
447
     *
448
     * ```php
449
     * $db->cache(function (Connection $db) {
450
     *
451
     *     // ... queries that use query cache ...
452
     *
453
     *     return $db->noCache(function (Connection $db) {
454
     *         // this query will not use query cache
455
     *         return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();
456
     *     });
457
     * });
458
     * ```
459
     *
460
     * @param callable $callable a PHP callable that contains DB queries which should not use query cache.
461
     * The signature of the callable is `function (Connection $db)`.
462
     * @return mixed the return result of the callable
463
     * @throws \Exception|\Throwable if there is any exception during query
464
     * @see enableQueryCache
465
     * @see queryCache
466
     * @see cache()
467
     */
468 3
    public function noCache(callable $callable)
469
    {
470 3
        $this->_queryCacheInfo[] = false;
471
        try {
472 3
            $result = call_user_func($callable, $this);
473 3
            array_pop($this->_queryCacheInfo);
474 3
            return $result;
475
        } catch (\Exception $e) {
476
            array_pop($this->_queryCacheInfo);
477
            throw $e;
478
        } catch (\Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
479
            array_pop($this->_queryCacheInfo);
480
            throw $e;
481
        }
482
    }
483
484
    /**
485
     * Returns the current query cache information.
486
     * This method is used internally by [[Command]].
487
     * @param int $duration the preferred caching duration. If null, it will be ignored.
488
     * @param \yii\caching\Dependency $dependency the preferred caching dependency. If null, it will be ignored.
489
     * @return array the current query cache information, or null if query cache is not enabled.
490
     * @internal
491
     */
492 637
    public function getQueryCacheInfo($duration, $dependency)
493
    {
494 637
        if (!$this->enableQueryCache) {
495 18
            return null;
496
        }
497
498 636
        $info = end($this->_queryCacheInfo);
499 636
        if (is_array($info)) {
500 3
            if ($duration === null) {
501 3
                $duration = $info[0];
502 3
            }
503 3
            if ($dependency === null) {
504 3
                $dependency = $info[1];
505 3
            }
506 3
        }
507
508 636
        if ($duration === 0 || $duration > 0) {
509 3
            if (is_string($this->queryCache) && Yii::$app) {
510
                $cache = Yii::$app->get($this->queryCache, false);
511
            } else {
512 3
                $cache = $this->queryCache;
513
            }
514 3
            if ($cache instanceof Cache) {
515 3
                return [$cache, $duration, $dependency];
516
            }
517
        }
518
519 636
        return null;
520
    }
521
522
    /**
523
     * Establishes a DB connection.
524
     * It does nothing if a DB connection has already been established.
525
     * @throws Exception if connection fails
526
     */
527 791
    public function open()
528
    {
529 791
        if ($this->pdo !== null) {
530 667
            return;
531
        }
532
533 690
        if (!empty($this->masters)) {
534 1
            $db = $this->openFromPool($this->masters, $this->masterConfig);
535 1
            if ($db !== null) {
536 1
                $this->pdo = $db->pdo;
537 1
                return;
538
            } else {
539
                throw new InvalidConfigException('None of the master DB servers is available.');
540
            }
541
        }
542
543 690
        if (empty($this->dsn)) {
544
            throw new InvalidConfigException('Connection::dsn cannot be empty.');
545
        }
546 690
        $token = 'Opening DB connection: ' . $this->dsn;
547
        try {
548 690
            Yii::info($token, __METHOD__);
549 690
            Yii::beginProfile($token, __METHOD__);
550 690
            $this->pdo = $this->createPdoInstance();
551 690
            $this->initConnection();
552 690
            Yii::endProfile($token, __METHOD__);
553 690
        } catch (\PDOException $e) {
554 3
            Yii::endProfile($token, __METHOD__);
555 3
            throw new Exception($e->getMessage(), $e->errorInfo, (int) $e->getCode(), $e);
556
        }
557 690
    }
558
559
    /**
560
     * Closes the currently active DB connection.
561
     * It does nothing if the connection is already closed.
562
     */
563 959
    public function close()
564
    {
565 959
        if ($this->pdo !== null) {
566 639
            Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__);
567 639
            $this->pdo = null;
568 639
            $this->_schema = null;
569 639
            $this->_transaction = null;
570 639
        }
571
572 959
        if ($this->_slave) {
573
            $this->_slave->close();
574
            $this->_slave = null;
575
        }
576 959
    }
577
578
    /**
579
     * Creates the PDO instance.
580
     * This method is called by [[open]] to establish a DB connection.
581
     * The default implementation will create a PHP PDO instance.
582
     * You may override this method if the default PDO needs to be adapted for certain DBMS.
583
     * @return PDO the pdo instance
584
     */
585 690
    protected function createPdoInstance()
586
    {
587 690
        $pdoClass = $this->pdoClass;
588 690
        if ($pdoClass === null) {
589 690
            $pdoClass = 'PDO';
590 690
            if ($this->_driverName !== null) {
591 67
                $driver = $this->_driverName;
592 690
            } elseif (($pos = strpos($this->dsn, ':')) !== false) {
593 625
                $driver = strtolower(substr($this->dsn, 0, $pos));
594 625
            }
595 690
            if (isset($driver)) {
596 690
                if ($driver === 'mssql' || $driver === 'dblib') {
597
                    $pdoClass = 'yii\db\mssql\PDO';
598 690
                } elseif ($driver === 'sqlsrv') {
599
                    $pdoClass = 'yii\db\mssql\SqlsrvPDO';
600
                }
601 690
            }
602 690
        }
603
604 690
        $dsn = $this->dsn;
605 690
        if (strncmp('sqlite:@', $dsn, 8) === 0) {
606 1
            $dsn = 'sqlite:' . Yii::getAlias(substr($dsn, 7));
607 1
        }
608 690
        return new $pdoClass($dsn, $this->username, $this->password, $this->attributes);
609
    }
610
611
    /**
612
     * Initializes the DB connection.
613
     * This method is invoked right after the DB connection is established.
614
     * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
615
     * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
616
     * It then triggers an [[EVENT_AFTER_OPEN]] event.
617
     */
618 690
    protected function initConnection()
619
    {
620 690
        $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
621 690
        if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
622
            $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
623
        }
624 690
        if ($this->charset !== null && in_array($this->getDriverName(), ['pgsql', 'mysql', 'mysqli', 'cubrid'], true)) {
625
            $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
626
        }
627 690
        $this->trigger(self::EVENT_AFTER_OPEN);
628 690
    }
629
630
    /**
631
     * Creates a command for execution.
632
     * @param string $sql the SQL statement to be executed
633
     * @param array $params the parameters to be bound to the SQL statement
634
     * @return Command the DB command
635
     */
636 695
    public function createCommand($sql = null, $params = [])
637
    {
638
        /** @var Command $command */
639 695
        $command = new $this->commandClass([
640 695
            'db' => $this,
641 695
            'sql' => $sql,
642 695
        ]);
643
644 695
        return $command->bindValues($params);
645
    }
646
647
    /**
648
     * Returns the currently active transaction.
649
     * @return Transaction the currently active transaction. Null if no active transaction.
650
     */
651 665
    public function getTransaction()
652
    {
653 665
        return $this->_transaction && $this->_transaction->getIsActive() ? $this->_transaction : null;
654
    }
655
656
    /**
657
     * Starts a transaction.
658
     * @param string|null $isolationLevel The isolation level to use for this transaction.
659
     * See [[Transaction::begin()]] for details.
660
     * @return Transaction the transaction initiated
661
     */
662 18
    public function beginTransaction($isolationLevel = null)
663
    {
664 18
        $this->open();
665
666 18
        if (($transaction = $this->getTransaction()) === null) {
667 18
            $transaction = $this->_transaction = new Transaction(['db' => $this]);
668 18
        }
669 18
        $transaction->begin($isolationLevel);
670
671 18
        return $transaction;
672
    }
673
674
    /**
675
     * Executes callback provided in a transaction.
676
     *
677
     * @param callable $callback a valid PHP callback that performs the job. Accepts connection instance as parameter.
678
     * @param string|null $isolationLevel The isolation level to use for this transaction.
679
     * See [[Transaction::begin()]] for details.
680
     * @throws \Exception|\Throwable if there is any exception during query. In this case the transaction will be rolled back.
681
     * @return mixed result of callback function
682
     */
683 12
    public function transaction(callable $callback, $isolationLevel = null)
684
    {
685 12
        $transaction = $this->beginTransaction($isolationLevel);
686 12
        $level = $transaction->level;
687
688
        try {
689 12
            $result = call_user_func($callback, $this);
690 9
            if ($transaction->isActive && $transaction->level === $level) {
691 9
                $transaction->commit();
692 9
            }
693 12
        } catch (\Exception $e) {
694 3
            if ($transaction->isActive && $transaction->level === $level) {
695 3
                $transaction->rollBack();
696 3
            }
697 3
            throw $e;
698
        } catch (\Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
699
            if ($transaction->isActive && $transaction->level === $level) {
700
                $transaction->rollBack();
701
            }
702
            throw $e;
703
        }
704
705 9
        return $result;
706
    }
707
708
    /**
709
     * Returns the schema information for the database opened by this connection.
710
     * @return Schema the schema information for the database opened by this connection.
711
     * @throws NotSupportedException if there is no support for the current driver type
712
     */
713 904
    public function getSchema()
714
    {
715 904
        if ($this->_schema !== null) {
716 749
            return $this->_schema;
717
        } else {
718 803
            $driver = $this->getDriverName();
719 803
            if (isset($this->schemaMap[$driver])) {
720 803
                $config = !is_array($this->schemaMap[$driver]) ? ['class' => $this->schemaMap[$driver]] : $this->schemaMap[$driver];
721 803
                $config['db'] = $this;
722
723 803
                return $this->_schema = Yii::createObject($config);
724
            } else {
725
                throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
726
            }
727
        }
728
    }
729
730
    /**
731
     * Returns the query builder for the current DB connection.
732
     * @return QueryBuilder the query builder for the current DB connection.
733
     */
734 565
    public function getQueryBuilder()
735
    {
736 565
        return $this->getSchema()->getQueryBuilder();
737
    }
738
739
    /**
740
     * Obtains the schema information for the named table.
741
     * @param string $name table name.
742
     * @param bool $refresh whether to reload the table schema even if it is found in the cache.
743
     * @return TableSchema table schema information. Null if the named table does not exist.
744
     */
745 80
    public function getTableSchema($name, $refresh = false)
746
    {
747 80
        return $this->getSchema()->getTableSchema($name, $refresh);
748
    }
749
750
    /**
751
     * Returns the ID of the last inserted row or sequence value.
752
     * @param string $sequenceName name of the sequence object (required by some DBMS)
753
     * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
754
     * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
755
     */
756
    public function getLastInsertID($sequenceName = '')
757
    {
758
        return $this->getSchema()->getLastInsertID($sequenceName);
759
    }
760
761
    /**
762
     * Quotes a string value for use in a query.
763
     * Note that if the parameter is not a string, it will be returned without change.
764
     * @param string $value string to be quoted
765
     * @return string the properly quoted string
766
     * @see http://www.php.net/manual/en/function.PDO-quote.php
767
     */
768 462
    public function quoteValue($value)
769
    {
770 462
        return $this->getSchema()->quoteValue($value);
771
    }
772
773
    /**
774
     * Quotes a table name for use in a query.
775
     * If the table name contains schema prefix, the prefix will also be properly quoted.
776
     * If the table name is already quoted or contains special characters including '(', '[[' and '{{',
777
     * then this method will do nothing.
778
     * @param string $name table name
779
     * @return string the properly quoted table name
780
     */
781 646
    public function quoteTableName($name)
782
    {
783 646
        return $this->getSchema()->quoteTableName($name);
784
    }
785
786
    /**
787
     * Quotes a column name for use in a query.
788
     * If the column name contains prefix, the prefix will also be properly quoted.
789
     * If the column name is already quoted or contains special characters including '(', '[[' and '{{',
790
     * then this method will do nothing.
791
     * @param string $name column name
792
     * @return string the properly quoted column name
793
     */
794 713
    public function quoteColumnName($name)
795
    {
796 713
        return $this->getSchema()->quoteColumnName($name);
797
    }
798
799
    /**
800
     * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
801
     * Tokens enclosed within double curly brackets are treated as table names, while
802
     * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
803
     * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
804
     * with [[tablePrefix]].
805
     * @param string $sql the SQL to be quoted
806
     * @return string the quoted SQL
807
     */
808 701
    public function quoteSql($sql)
809
    {
810 701
        return preg_replace_callback(
811 701
            '/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
812 701
            function ($matches) {
813 287
                if (isset($matches[3])) {
814 230
                    return $this->quoteColumnName($matches[3]);
815
                } else {
816 248
                    return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
817
                }
818 701
            },
819
            $sql
820 701
        );
821
    }
822
823
    /**
824
     * Returns the name of the DB driver. Based on the the current [[dsn]], in case it was not set explicitly
825
     * by an end user.
826
     * @return string name of the DB driver
827
     */
828 830
    public function getDriverName()
829
    {
830 830
        if ($this->_driverName === null) {
831 806
            if (($pos = strpos($this->dsn, ':')) !== false) {
832 806
                $this->_driverName = strtolower(substr($this->dsn, 0, $pos));
833 806
            } else {
834
                $this->_driverName = strtolower($this->getSlavePdo()->getAttribute(PDO::ATTR_DRIVER_NAME));
835
            }
836 806
        }
837 830
        return $this->_driverName;
838
    }
839
840
    /**
841
     * Changes the current driver name.
842
     * @param string $driverName name of the DB driver
843
     */
844
    public function setDriverName($driverName)
845
    {
846
        $this->_driverName = strtolower($driverName);
847
    }
848
849
    /**
850
     * Returns the PDO instance for the currently active slave connection.
851
     * When [[enableSlaves]] is true, one of the slaves will be used for read queries, and its PDO instance
852
     * will be returned by this method.
853
     * @param bool $fallbackToMaster whether to return a master PDO in case none of the slave connections is available.
854
     * @return PDO the PDO instance for the currently active slave connection. Null is returned if no slave connection
855
     * is available and `$fallbackToMaster` is false.
856
     */
857 651
    public function getSlavePdo($fallbackToMaster = true)
858
    {
859 651
        $db = $this->getSlave(false);
860 651
        if ($db === null) {
861 651
            return $fallbackToMaster ? $this->getMasterPdo() : null;
862
        } else {
863 1
            return $db->pdo;
864
        }
865
    }
866
867
    /**
868
     * Returns the PDO instance for the currently active master connection.
869
     * This method will open the master DB connection and then return [[pdo]].
870
     * @return PDO the PDO instance for the currently active master connection.
871
     */
872 673
    public function getMasterPdo()
873
    {
874 673
        $this->open();
875 673
        return $this->pdo;
876
    }
877
878
    /**
879
     * Returns the currently active slave connection.
880
     * If this method is called the first time, it will try to open a slave connection when [[enableSlaves]] is true.
881
     * @param bool $fallbackToMaster whether to return a master connection in case there is no slave connection available.
882
     * @return Connection the currently active slave connection. Null is returned if there is slave available and
883
     * `$fallbackToMaster` is false.
884
     */
885 651
    public function getSlave($fallbackToMaster = true)
886
    {
887 651
        if (!$this->enableSlaves) {
888 107
            return $fallbackToMaster ? $this : null;
889
        }
890
891 558
        if ($this->_slave === false) {
892 558
            $this->_slave = $this->openFromPool($this->slaves, $this->slaveConfig);
893 558
        }
894
895 558
        return $this->_slave === null && $fallbackToMaster ? $this : $this->_slave;
896
    }
897
898
    /**
899
     * Executes the provided callback by using the master connection.
900
     *
901
     * This method is provided so that you can temporarily force using the master connection to perform
902
     * DB operations even if they are read queries. For example,
903
     *
904
     * ```php
905
     * $result = $db->useMaster(function ($db) {
906
     *     return $db->createCommand('SELECT * FROM user LIMIT 1')->queryOne();
907
     * });
908
     * ```
909
     *
910
     * @param callable $callback a PHP callable to be executed by this method. Its signature is
911
     * `function (Connection $db)`. Its return value will be returned by this method.
912
     * @return mixed the return value of the callback
913
     */
914 1
    public function useMaster(callable $callback)
915
    {
916 1
        $enableSlave = $this->enableSlaves;
917 1
        $this->enableSlaves = false;
918 1
        $result = call_user_func($callback, $this);
919 1
        $this->enableSlaves = $enableSlave;
920 1
        return $result;
921
    }
922
923
    /**
924
     * Opens the connection to a server in the pool.
925
     * This method implements the load balancing among the given list of the servers.
926
     * @param array $pool the list of connection configurations in the server pool
927
     * @param array $sharedConfig the configuration common to those given in `$pool`.
928
     * @return Connection the opened DB connection, or null if no server is available
929
     * @throws InvalidConfigException if a configuration does not specify "dsn"
930
     */
931 558
    protected function openFromPool(array $pool, array $sharedConfig)
932
    {
933 558
        if (empty($pool)) {
934 557
            return null;
935
        }
936
937 1
        if (!isset($sharedConfig['class'])) {
938 1
            $sharedConfig['class'] = get_class($this);
939 1
        }
940
941 1
        $cache = is_string($this->serverStatusCache) ? Yii::$app->get($this->serverStatusCache, false) : $this->serverStatusCache;
942
943 1
        shuffle($pool);
944
945 1
        foreach ($pool as $config) {
946 1
            $config = array_merge($sharedConfig, $config);
947 1
            if (empty($config['dsn'])) {
948
                throw new InvalidConfigException('The "dsn" option must be specified.');
949
            }
950
951 1
            $key = [__METHOD__, $config['dsn']];
952 1
            if ($cache instanceof Cache && $cache->get($key)) {
953
                // should not try this dead server now
954
                continue;
955
            }
956
957
            /* @var $db Connection */
958 1
            $db = Yii::createObject($config);
959
960
            try {
961 1
                $db->open();
962 1
                return $db;
963
            } catch (\Exception $e) {
964
                Yii::warning("Connection ({$config['dsn']}) failed: " . $e->getMessage(), __METHOD__);
965
                if ($cache instanceof Cache) {
966
                    // mark this server as dead and only retry it after the specified interval
967
                    $cache->set($key, 1, $this->serverRetryInterval);
968
                }
969
            }
970
        }
971
972
        return null;
973
    }
974
975
    /**
976
     * Close the connection before serializing.
977
     * @return array
978
     */
979 4
    public function __sleep()
980
    {
981 4
        $this->close();
982 4
        return array_keys((array) $this);
983
    }
984
}
985