Completed
Push — fix-numbervalidator-comma-deci... ( cad400...eee7b9 )
by Alexander
61:03 queued 21:06
created

Connection::getSlave()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 6

Importance

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