Issues (1474)

framework/Data/TDbConnection.php (8 issues)

1
<?php
2
3
/**
4
 * TDbConnection class file
5
 *
6
 * @author Qiang Xue <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Data;
12
13
use PDO;
14
use PDOException;
15
use Prado\Data\Common\TDbMetaData;
16
use Prado\Exceptions\TDbException;
17
use Prado\Prado;
18
use Prado\TPropertyValue;
19
20
/**
21
 * TDbConnection class
22
 *
23
 * TDbConnection represents a connection to a database.
24
 *
25
 * TDbConnection works together with {@see \Prado\Data\TDbCommand}, {@see \Prado\Data\TDbDataReader}
26
 * and {@see \Prado\Data\TDbTransaction} to provide data access to various DBMS
27
 * in a common set of APIs. They are a thin wrapper of the {@see http://www.php.net/manual/en/ref.pdo.php PDO}
28
 * PHP extension.
29
 *
30
 * To establish a connection, set {@see setActive Active} to true after
31
 * specifying {@see setConnectionString ConnectionString}, {@see setUsername Username}
32
 * and {@see setPassword Password}.
33
 *
34
 * Since 3.1.2, the connection charset can be set (for MySQL and PostgreSQL databases only)
35
 * using the {@see setCharset Charset} property. The value of this property is database dependant.
36
 * e.g. for mysql, you can use 'latin1' for cp1252 West European, 'utf8' for unicode, ...
37
 *
38
 * The following example shows how to create a TDbConnection instance and establish
39
 * the actual connection:
40
 * ```php
41
 * $connection=new TDbConnection($dsn,$username,$password);
42
 * $connection->Active=true;
43
 * ```
44
 *
45
 * After the DB connection is established, one can execute an SQL statement like the following:
46
 * ```php
47
 * $command=$connection->createCommand($sqlStatement);
48
 * $command->execute();   // a non-query SQL statement execution
49
 * // or execute an SQL query and fetch the result set
50
 * $reader=$command->query();
51
 *
52
 * // each $row is an array representing a row of data
53
 * foreach($reader as $row) ...
54
 * ```
55
 *
56
 * One can do prepared SQL execution and bind parameters to the prepared SQL:
57
 * ```php
58
 * $command=$connection->createCommand($sqlStatement);
59
 * $command->bindParameter($name1,$value1);
60
 * $command->bindParameter($name2,$value2);
61
 * $command->execute();
62
 * ```
63
 *
64
 * To use transaction, do like the following:
65
 * ```php
66
 * $transaction=$connection->beginTransaction();
67
 * try
68
 * {
69
 *    $connection->createCommand($sql1)->execute();
70
 *    $connection->createCommand($sql2)->execute();
71
 *    //.... other SQL executions
72
 *    $transaction->commit();
73
 * }
74
 * catch(Exception $e)
75
 * {
76
 *    $transaction->rollBack();
77
 * }
78
 * ```
79
 *
80
 * TDbConnection provides a set of methods to support setting and querying
81
 * of certain DBMS attributes, such as {@see getNullConversion NullConversion}.
82
 *
83
 * @author Qiang Xue <[email protected]>
84
 * @since 3.0
85
 */
86
class TDbConnection extends \Prado\TComponent
87
{
88
	/**
89
	 *
90
	 * @since 3.1.7
91
	 */
92
	public const DEFAULT_TRANSACTION_CLASS = \Prado\Data\TDbTransaction::class;
93
94
	private $_dsn = '';
95
	private $_username = '';
96
	private $_password = '';
97
	private $_charset = '';
98
	private $_attributes = [];
99
	private $_active = false;
100
	private $_pdo;
101
	private $_transaction;
102
103
	/**
104
	 * @var TDbMetaData
105
	 */
106
	private $_dbMeta;
107
108
	/**
109
	 * @var string
110
	 * @since 3.1.7
111
	 */
112
	private $_transactionClass = self::DEFAULT_TRANSACTION_CLASS;
113
114
	/**
115
	 * Constructor.
116
	 * Note, the DB connection is not established when this connection
117
	 * instance is created. Set {@see setActive Active} property to true
118
	 * to establish the connection.
119
	 * Since 3.1.2, you can set the charset for MySql connection
120
	 *
121
	 * @param string $dsn The Data Source Name, or DSN, contains the information required to connect to the database.
122
	 * @param string $username The user name for the DSN string.
123
	 * @param string $password The password for the DSN string.
124
	 * @param string $charset Charset used for DB Connection (MySql & pgsql only). If not set, will use the default charset of your database server
125
	 * @see http://www.php.net/manual/en/function.PDO-construct.php
126
	 */
127
	public function __construct($dsn = '', $username = '', #[\SensitiveParameter] $password = '', $charset = '')
128 78
	{
129
		$this->_dsn = $dsn;
130 78
		$this->_username = $username;
131 78
		$this->_password = $password;
132 78
		$this->_charset = $charset;
133 78
		parent::__construct();
134 78
	}
135
136
	/**
137
	 * Close the connection when serializing.
138
	 */
139 3
	public function __sleep()
140
	{
141
		/*
142 3
		 * $this->close();
143
		 * DO NOT CLOSE the current connection as serializing doesn't necessarily mean
144
		 * we don't this connection anymore in the current session
145
		 */
146
		return array_diff(parent::__sleep(), ["\0Prado\Data\TDbConnection\0_pdo", "\0Prado\Data\TDbConnection\0_active"]);
147
	}
148
149
	/**
150
	 * @return array list of available PDO drivers
151
	 * @see http://www.php.net/manual/en/function.PDO-getAvailableDrivers.php
152
	 */
153
	public static function getAvailableDrivers()
154
	{
155
		return PDO::getAvailableDrivers();
156
	}
157 199
158
	/**
159 199
	 * @return bool whether the DB connection is established
160
	 */
161
	public function getActive()
162
	{
163
		return $this->_active;
164
	}
165
166
	/**
167 201
	 * Open or close the DB connection.
168
	 * @param bool $value whether to open or close DB connection
169 201
	 * @throws TDbException if connection fails
170 201
	 */
171 164
	public function setActive($value)
172 164
	{
173
		$value = TPropertyValue::ensureBoolean($value);
174 2
		if ($value !== $this->_active) {
175
			if ($value) {
176
				$this->open();
177 201
			} else {
178
				$this->close();
179
			}
180
		}
181
	}
182
183 164
	/**
184
	 * Opens DB connection if it is currently not
185 164
	 * @throws TDbException if connection fails
186
	 */
187 164
	protected function open()
188 164
	{
189 164
		if ($this->_pdo === null) {
190 164
			try {
191 164
				$this->_pdo = new PDO(
192
					$this->getConnectionString(),
193
					$this->getUsername(),
194
					$this->getPassword(),
195 164
					$this->_attributes
196 164
				);
197 164
				// This attribute is only useful for PDO::MySql driver.
198 164
				// Ignore the warning if a driver doesn't understand this.
199 1
				@$this->_pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for setAttribute(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

199
				/** @scrutinizer ignore-unhandled */ @$this->_pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
200 1
				// This attribute is only useful for PDO::MySql driver since PHP 8.1
201
				// This ensures integers are returned as strings (needed eg. for ZEROFILL columns)
202
				@$this->_pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
203 164
				$this->_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
204
				$this->_active = true;
205
				$this->setConnectionCharset();
206
			} catch (PDOException $e) {
207
				throw new TDbException('dbconnection_open_failed', $e->getMessage());
208
			}
209 2
		}
210
	}
211 2
212 2
	/**
213 2
	 * Closes the currently active DB connection.
214
	 * It does nothing if the connection is already closed.
215
	 */
216
	protected function close()
217
	{
218
		$this->_pdo = null;
219
		$this->_active = false;
220 164
	}
221
222 164
	/*
223 164
	 * Set the database connection charset.
224
	 * Only MySql databases are supported for now.
225
	 * @since 3.1.2
226
	 */
227
	protected function setConnectionCharset()
228
	{
229
		if ($this->_charset === '' || $this->_active === false) {
230
			return;
231
		}
232
		$driver = $this->_pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
233
		switch ($driver) {
234
			case 'mysql':
235
			case 'sqlite':
236
				$stmt = $this->_pdo->prepare('SET NAMES ?');
237
				break;
238
			case 'pgsql':
239
				$stmt = $this->_pdo->prepare('SET client_encoding TO ?');
240
				break;
241
			default:
242 198
				throw new TDbException('dbconnection_unsupported_driver_charset', $driver);
243
		}
244 198
		$stmt->execute([$this->_charset]);
245
	}
246
247
	/**
248
	 * @return string The Data Source Name, or DSN, contains the information required to connect to the database.
249
	 */
250
	public function getConnectionString()
251
	{
252
		return $this->_dsn;
253
	}
254
255
	/**
256
	 * @param string $value The Data Source Name, or DSN, contains the information required to connect to the database.
257
	 * @see http://www.php.net/manual/en/function.PDO-construct.php
258
	 */
259 164
	public function setConnectionString($value)
260
	{
261 164
		$this->_dsn = $value;
262
	}
263
264
	/**
265
	 * @return string the username for establishing DB connection. Defaults to empty string.
266
	 */
267
	public function getUsername()
268
	{
269
		return $this->_username;
270
	}
271
272
	/**
273
	 * @param string $value the username for establishing DB connection
274
	 */
275 164
	public function setUsername($value)
276
	{
277 164
		$this->_username = $value;
278
	}
279
280
	/**
281
	 * @return string the password for establishing DB connection. Defaults to empty string.
282
	 */
283
	public function getPassword()
284
	{
285
		return $this->_password;
286
	}
287
288
	/**
289
	 * @param string $value the password for establishing DB connection
290
	 */
291
	public function setPassword(#[\SensitiveParameter] $value)
292
	{
293
		$this->_password = $value;
294
	}
295
296
	/**
297
	 * @return string the charset used for database connection. Defaults to emtpy string.
298
	 */
299
	public function getCharset()
300
	{
301
		return $this->_charset;
302
	}
303
304
	/**
305
	 * @param string $value the charset used for database connection
306
	 */
307
	public function setCharset($value)
308 199
	{
309
		$this->_charset = $value;
310 199
		$this->setConnectionCharset();
311
	}
312
313
	/**
314
	 * @return PDO the PDO instance, null if the connection is not established yet
315
	 */
316
	public function getPdoInstance()
317
	{
318
		return $this->_pdo;
319 199
	}
320
321 199
	/**
322 199
	 * Creates a command for execution.
323
	 * @param string $sql SQL statement associated with the new command.
324 1
	 * @throws TDbException if the connection is not active
325
	 * @return TDbCommand the DB command
326
	 */
327
	public function createCommand($sql)
328
	{
329
		if ($this->getActive()) {
330
			return new TDbCommand($this, $sql);
331
		} else {
332
			throw new TDbException('dbconnection_connection_inactive');
333
		}
334
	}
335
336
	/**
337
	 * @return TDbTransaction the currently active transaction. Null if no active transaction.
338
	 */
339
	public function getCurrentTransaction()
340
	{
341
		if ($this->_transaction !== null) {
342
			if ($this->_transaction->getActive()) {
343
				return $this->_transaction;
344
			}
345
		}
346 3
		return null;
347
	}
348 3
349 3
	/**
350 3
	 * Starts a transaction.
351
	 * @throws TDbException if the connection is not active
352
	 * @return TDbTransaction the transaction initiated
353
	 */
354
	public function beginTransaction()
355
	{
356
		if ($this->getActive()) {
357
			$this->_pdo->beginTransaction();
358
			return $this->_transaction = Prado::createComponent($this->getTransactionClass(), $this);
359
		} else {
360 3
			throw new TDbException('dbconnection_connection_inactive');
361
		}
362 3
	}
363
364
	/**
365
	 * @return string Transaction class name to be created by calling {@see \Prado\Data\TDbConnection::beginTransaction}. Defaults to '\Prado\Data\TDbTransaction'.
366
	 * @since 3.1.7
367
	 */
368
	public function getTransactionClass()
369
	{
370
		return $this->_transactionClass;
371
	}
372
373
374
	/**
375
	 * @param string $value Transaction class name to be created by calling {@see \Prado\Data\TDbConnection::beginTransaction}.
376
	 * @since 3.1.7
377
	 */
378
	public function setTransactionClass($value)
379
	{
380
		$this->_transactionClass = (string) $value;
381 2
	}
382
383 2
	/**
384 2
	 * Returns the ID of the last inserted row or sequence value.
385
	 * @param string $sequenceName name of the sequence object (required by some DBMS)
386
	 * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
387
	 * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
388
	 */
389
	public function getLastInsertID($sequenceName = '')
390
	{
391
		if ($this->getActive()) {
392
			return $this->_pdo->lastInsertId($sequenceName);
393
		} else {
394
			throw new TDbException('dbconnection_connection_inactive');
395
		}
396 16
	}
397
398 16
	/**
399 16
	 * Quotes a string for use in a query.
400
	 * @param string $str string to be quoted
401
	 * @return string the properly quoted string
402
	 * @see http://www.php.net/manual/en/function.PDO-quote.php
403
	 */
404
	public function quoteString($str)
405
	{
406
		if ($this->getActive()) {
407
			return $this->_pdo->quote($str);
408
		} else {
409
			throw new TDbException('dbconnection_connection_inactive');
410
		}
411
	}
412
413
	/**
414
	 * Quotes a table name for use in a query.
415
	 * @param string $name $name table name
416
	 * @return string the properly quoted table name
417
	 */
418
	public function quoteTableName($name)
419
	{
420
		return $this->getDbMetaData()->quoteTableName($name);
421
	}
422
423
	/**
424
	 * Quotes a column name for use in a query.
425
	 * @param string $name $name column name
426
	 * @return string the properly quoted column name
427
	 */
428
	public function quoteColumnName($name)
429
	{
430
		return $this->getDbMetaData()->quoteColumnName($name);
431
	}
432
433
	/**
434
	 * Quotes a column alias for use in a query.
435
	 * @param string $name $name column name
436
	 * @return string the properly quoted column alias
437
	 */
438
	public function quoteColumnAlias($name)
439
	{
440
		return $this->getDbMetaData()->quoteColumnAlias($name);
441
	}
442
443
	/**
444
	 * @return TDbMetaData
445
	 */
446
	public function getDbMetaData()
447
	{
448
		if ($this->_dbMeta === null) {
449 12
			$this->_dbMeta = TDbMetaData::getInstance($this);
450
		}
451 12
		return $this->_dbMeta;
452 12
	}
453 12
454 1
	/**
455 1
	 * @return TDbColumnCaseMode the case of the column names
456
	 */
457
	public function getColumnCase()
458
	{
459
		switch ($this->getAttribute(PDO::ATTR_CASE)) {
460
			case PDO::CASE_LOWER:
461
				return TDbColumnCaseMode::LowerCase;
0 ignored issues
show
Bug Best Practice introduced by
The expression return Prado\Data\TDbColumnCaseMode::LowerCase returns the type string which is incompatible with the documented return type Prado\Data\TDbColumnCaseMode.
Loading history...
462
			case PDO::CASE_UPPER:
463
				return TDbColumnCaseMode::UpperCase;
0 ignored issues
show
Bug Best Practice introduced by
The expression return Prado\Data\TDbColumnCaseMode::UpperCase returns the type string which is incompatible with the documented return type Prado\Data\TDbColumnCaseMode.
Loading history...
464 1
			case PDO::CASE_NATURAL:
465
			default:
466 1
				return TDbColumnCaseMode::Preserved;
0 ignored issues
show
Bug Best Practice introduced by
The expression return Prado\Data\TDbColumnCaseMode::Preserved returns the type string which is incompatible with the documented return type Prado\Data\TDbColumnCaseMode.
Loading history...
467 1
		}
468
	}
469
470 1
	/**
471 1
	 * @param TDbColumnCaseMode $value the case of the column names
472 1
	 */
473
	public function setColumnCase($value)
474
	{
475
		switch (TPropertyValue::ensureEnum($value, TDbColumnCaseMode::class)) {
476
			case TDbColumnCaseMode::Preserved:
477 1
				$value = PDO::CASE_NATURAL;
478 1
				break;
479
			case TDbColumnCaseMode::LowerCase:
480
				$value = PDO::CASE_LOWER;
481
				break;
482
			case TDbColumnCaseMode::UpperCase:
483 1
				$value = PDO::CASE_UPPER;
484
				break;
485 1
		}
486 1
		$this->setAttribute(PDO::ATTR_CASE, $value);
487 1
	}
488 1
489
	/**
490 1
	 * @return TDbNullConversionMode how the null and empty strings are converted
491 1
	 */
492
	public function getNullConversion()
493
	{
494
		switch ($this->getAttribute(PDO::ATTR_ORACLE_NULLS)) {
495
			case PDO::NULL_EMPTY_STRING:
496
				return TDbNullConversionMode::EmptyStringToNull;
0 ignored issues
show
Bug Best Practice introduced by
The expression return Prado\Data\TDbNul...Mode::EmptyStringToNull returns the type string which is incompatible with the documented return type Prado\Data\TDbNullConversionMode.
Loading history...
497
			case PDO::NULL_TO_STRING:
498 1
				return TDbNullConversionMode::NullToEmptyString;
0 ignored issues
show
Bug Best Practice introduced by
The expression return Prado\Data\TDbNul...Mode::NullToEmptyString returns the type string which is incompatible with the documented return type Prado\Data\TDbNullConversionMode.
Loading history...
499
			case PDO::NULL_NATURAL:
500 1
			default:
501 1
				return TDbNullConversionMode::Preserved;
0 ignored issues
show
Bug Best Practice introduced by
The expression return Prado\Data\TDbNullConversionMode::Preserved returns the type string which is incompatible with the documented return type Prado\Data\TDbNullConversionMode.
Loading history...
502
		}
503
	}
504 1
505
	/**
506
	 * @param TDbNullConversionMode $value how the null and empty strings are converted
507 1
	 */
508 1
	public function setNullConversion($value)
509 1
	{
510
		switch (TPropertyValue::ensureEnum($value, TDbNullConversionMode::class)) {
511 1
			case TDbNullConversionMode::Preserved:
512 1
				$value = PDO::NULL_NATURAL;
513
				break;
514
			case TDbNullConversionMode::EmptyStringToNull:
515
				$value = PDO::NULL_EMPTY_STRING;
516
				break;
517
			case TDbNullConversionMode::NullToEmptyString:
518
				$value = PDO::NULL_TO_STRING;
519
				break;
520
		}
521
		$this->setAttribute(PDO::ATTR_ORACLE_NULLS, $value);
522
	}
523
524
	/**
525
	 * @return bool whether creating or updating a DB record will be automatically committed.
526
	 * Some DBMS (such as sqlite) may not support this feature.
527
	 */
528
	public function getAutoCommit()
529
	{
530
		return $this->getAttribute(PDO::ATTR_AUTOCOMMIT);
531
	}
532
533
	/**
534
	 * @param bool $value whether creating or updating a DB record will be automatically committed.
535
	 * Some DBMS (such as sqlite) may not support this feature.
536
	 */
537
	public function setAutoCommit($value)
538
	{
539
		$this->setAttribute(PDO::ATTR_AUTOCOMMIT, TPropertyValue::ensureBoolean($value));
540
	}
541
542
	/**
543
	 * @return bool whether the connection is persistent or not
544
	 * Some DBMS (such as sqlite) may not support this feature.
545
	 */
546
	public function getPersistent()
547
	{
548
		return $this->getAttribute(PDO::ATTR_PERSISTENT);
549
	}
550
551
	/**
552
	 * @param bool $value whether the connection is persistent or not
553 47
	 * Some DBMS (such as sqlite) may not support this feature.
554
	 */
555 47
	public function setPersistent($value)
556
	{
557
		return $this->setAttribute(PDO::ATTR_PERSISTENT, TPropertyValue::ensureBoolean($value));
0 ignored issues
show
Are you sure the usage of $this->setAttribute(PDO:...:ensureBoolean($value)) targeting Prado\Data\TDbConnection::setAttribute() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
558
	}
559
560
	/**
561
	 * @return string name of the DB driver
562
	 */
563
	public function getDriverName()
564
	{
565
		return $this->getAttribute(PDO::ATTR_DRIVER_NAME);
566
	}
567
568
	/**
569
	 * @return string the version information of the DB driver
570
	 */
571
	public function getClientVersion()
572
	{
573
		return $this->getAttribute(PDO::ATTR_CLIENT_VERSION);
574
	}
575
576
	/**
577
	 * @return string the status of the connection
578
	 * Some DBMS (such as sqlite) may not support this feature.
579
	 */
580
	public function getConnectionStatus()
581
	{
582
		return $this->getAttribute(PDO::ATTR_CONNECTION_STATUS);
583
	}
584
585
	/**
586
	 * @return bool whether the connection performs data prefetching
587
	 */
588
	public function getPrefetch()
589
	{
590
		return $this->getAttribute(PDO::ATTR_PREFETCH);
591
	}
592
593
	/**
594
	 * @return string the information of DBMS server
595
	 */
596
	public function getServerInfo()
597
	{
598
		return $this->getAttribute(PDO::ATTR_SERVER_INFO);
599
	}
600
601
	/**
602
	 * @return string the version information of DBMS server
603
	 */
604
	public function getServerVersion()
605
	{
606
		return $this->getAttribute(PDO::ATTR_SERVER_VERSION);
607
	}
608
609
	/**
610
	 * @return int timeout settings for the connection
611
	 */
612
	public function getTimeout()
613 53
	{
614
		return $this->getAttribute(PDO::ATTR_TIMEOUT);
615 53
	}
616 53
617
	/**
618
	 * Obtains a specific DB connection attribute information.
619
	 * @param int $name the attribute to be queried
620
	 * @return mixed the corresponding attribute information
621
	 * @see http://www.php.net/manual/en/function.PDO-getAttribute.php
622
	 */
623
	public function getAttribute($name)
624
	{
625
		if ($this->getActive()) {
626
			return $this->_pdo->getAttribute($name);
627
		} else {
628 2
			throw new TDbException('dbconnection_connection_inactive');
629
		}
630 2
	}
631 2
632
	/**
633
	 * Sets an attribute on the database connection.
634
	 * @param int $name the attribute to be set
635 2
	 * @param mixed $value the attribute value
636
	 * @see http://www.php.net/manual/en/function.PDO-setAttribute.php
637
	 */
638
	public function setAttribute($name, $value)
639
	{
640
		if ($this->_pdo instanceof PDO) {
641
			$this->_pdo->setAttribute($name, $value);
642
		} else {
643
			$this->_attributes[$name] = $value;
644
		}
645
	}
646
}
647