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