[ Index ] |
PHP Cross Reference of Joomla 4.2.2 documentation |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Part of the Joomla Framework Database Package 4 * 5 * @copyright Copyright (C) 2005 - 2021 Open Source Matters, Inc. All rights reserved. 6 * @license GNU General Public License version 2 or later; see LICENSE 7 */ 8 9 namespace Joomla\Database\Pdo; 10 11 use Joomla\Database\DatabaseDriver; 12 use Joomla\Database\DatabaseEvents; 13 use Joomla\Database\Event\ConnectionEvent; 14 use Joomla\Database\Exception\ConnectionFailureException; 15 use Joomla\Database\Exception\ExecutionFailureException; 16 use Joomla\Database\Exception\PrepareStatementFailureException; 17 use Joomla\Database\Exception\UnsupportedAdapterException; 18 use Joomla\Database\StatementInterface; 19 20 /** 21 * Joomla Framework PDO Database Driver Class 22 * 23 * @link https://www.php.net/pdo 24 * @since 1.0 25 */ 26 abstract class PdoDriver extends DatabaseDriver 27 { 28 /** 29 * The database connection resource. 30 * 31 * @var \PDO 32 * @since 1.0 33 */ 34 protected $connection; 35 36 /** 37 * The name of the database driver. 38 * 39 * @var string 40 * @since 1.0 41 */ 42 public $name = 'pdo'; 43 44 /** 45 * The character(s) used to quote SQL statement names such as table names or field names, etc. 46 * 47 * If a single character string the same character is used for both sides of the quoted name, else the first character will be used for the 48 * opening quote and the second for the closing quote. 49 * 50 * @var string 51 * @since 1.0 52 */ 53 protected $nameQuote = "'"; 54 55 /** 56 * The null or zero representation of a timestamp for the database driver. 57 * 58 * @var string 59 * @since 1.0 60 */ 61 protected $nullDate = '0000-00-00 00:00:00'; 62 63 /** 64 * Constructor. 65 * 66 * @param array $options List of options used to configure the connection 67 * 68 * @since 1.0 69 */ 70 public function __construct(array $options) 71 { 72 // Get some basic values from the options. 73 $options['driver'] = $options['driver'] ?? 'odbc'; 74 $options['dsn'] = $options['dsn'] ?? ''; 75 $options['host'] = $options['host'] ?? 'localhost'; 76 $options['database'] = $options['database'] ?? ''; 77 $options['user'] = $options['user'] ?? ''; 78 $options['port'] = isset($options['port']) ? (int) $options['port'] : null; 79 $options['password'] = $options['password'] ?? ''; 80 $options['driverOptions'] = $options['driverOptions'] ?? []; 81 $options['ssl'] = isset($options['ssl']) ? $options['ssl'] : []; 82 $options['socket'] = \strpos($options['host'], 'unix:') !== false ? \str_replace('unix:', '', $options['host']) : null; 83 84 if ($options['ssl'] !== []) 85 { 86 $options['ssl']['enable'] = isset($options['ssl']['enable']) ? $options['ssl']['enable'] : false; 87 $options['ssl']['cipher'] = isset($options['ssl']['cipher']) ? $options['ssl']['cipher'] : null; 88 $options['ssl']['ca'] = isset($options['ssl']['ca']) ? $options['ssl']['ca'] : null; 89 $options['ssl']['capath'] = isset($options['ssl']['capath']) ? $options['ssl']['capath'] : null; 90 $options['ssl']['key'] = isset($options['ssl']['key']) ? $options['ssl']['key'] : null; 91 $options['ssl']['cert'] = isset($options['ssl']['cert']) ? $options['ssl']['cert'] : null; 92 $options['ssl']['verify_server_cert'] = isset($options['ssl']['verify_server_cert']) ? $options['ssl']['verify_server_cert'] : null; 93 } 94 95 // Finalize initialisation 96 parent::__construct($options); 97 } 98 99 /** 100 * Destructor. 101 * 102 * @since 1.0 103 */ 104 public function __destruct() 105 { 106 $this->disconnect(); 107 } 108 109 /** 110 * Connects to the database if needed. 111 * 112 * @return void Returns void if the database connected successfully. 113 * 114 * @since 1.0 115 * @throws \RuntimeException 116 */ 117 public function connect() 118 { 119 if ($this->connection) 120 { 121 return; 122 } 123 124 // Make sure the PDO extension for PHP is installed and enabled. 125 if (!static::isSupported()) 126 { 127 throw new UnsupportedAdapterException('PDO Extension is not available.', 1); 128 } 129 130 // Find the correct PDO DSN Format to use: 131 switch ($this->options['driver']) 132 { 133 case 'cubrid': 134 $this->options['port'] = $this->options['port'] ?? 33000; 135 136 $format = 'cubrid:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 137 138 $replace = ['#HOST#', '#PORT#', '#DBNAME#']; 139 $with = [$this->options['host'], $this->options['port'], $this->options['database']]; 140 141 break; 142 143 case 'dblib': 144 $this->options['port'] = $this->options['port'] ?? 1433; 145 146 $format = 'dblib:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 147 148 $replace = ['#HOST#', '#PORT#', '#DBNAME#']; 149 $with = [$this->options['host'], $this->options['port'], $this->options['database']]; 150 151 break; 152 153 case 'firebird': 154 $this->options['port'] = $this->options['port'] ?? 3050; 155 156 $format = 'firebird:dbname=#DBNAME#'; 157 158 $replace = ['#DBNAME#']; 159 $with = [$this->options['database']]; 160 161 break; 162 163 case 'ibm': 164 $this->options['port'] = $this->options['port'] ?? 56789; 165 166 if (!empty($this->options['dsn'])) 167 { 168 $format = 'ibm:DSN=#DSN#'; 169 170 $replace = ['#DSN#']; 171 $with = [$this->options['dsn']]; 172 } 173 else 174 { 175 $format = 'ibm:hostname=#HOST#;port=#PORT#;database=#DBNAME#'; 176 177 $replace = ['#HOST#', '#PORT#', '#DBNAME#']; 178 $with = [$this->options['host'], $this->options['port'], $this->options['database']]; 179 } 180 181 break; 182 183 case 'informix': 184 $this->options['port'] = $this->options['port'] ?? 1526; 185 $this->options['protocol'] = $this->options['protocol'] ?? 'onsoctcp'; 186 187 if (!empty($this->options['dsn'])) 188 { 189 $format = 'informix:DSN=#DSN#'; 190 191 $replace = ['#DSN#']; 192 $with = [$this->options['dsn']]; 193 } 194 else 195 { 196 $format = 'informix:host=#HOST#;service=#PORT#;database=#DBNAME#;server=#SERVER#;protocol=#PROTOCOL#'; 197 198 $replace = ['#HOST#', '#PORT#', '#DBNAME#', '#SERVER#', '#PROTOCOL#']; 199 $with = [ 200 $this->options['host'], 201 $this->options['port'], 202 $this->options['database'], 203 $this->options['server'], 204 $this->options['protocol'], 205 ]; 206 } 207 208 break; 209 210 case 'mssql': 211 $this->options['port'] = $this->options['port'] ?? 1433; 212 213 $format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 214 215 $replace = ['#HOST#', '#PORT#', '#DBNAME#']; 216 $with = [$this->options['host'], $this->options['port'], $this->options['database']]; 217 218 break; 219 220 case 'mysql': 221 $this->options['port'] = $this->options['port'] ?? 3306; 222 223 if ($this->options['socket'] !== null) 224 { 225 $format = 'mysql:unix_socket=#SOCKET#;dbname=#DBNAME#;charset=#CHARSET#'; 226 } 227 else 228 { 229 $format = 'mysql:host=#HOST#;port=#PORT#;dbname=#DBNAME#;charset=#CHARSET#'; 230 } 231 232 $replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#', '#CHARSET#']; 233 $with = [ 234 $this->options['host'], 235 $this->options['port'], 236 $this->options['socket'], 237 $this->options['database'], 238 $this->options['charset'] 239 ]; 240 241 break; 242 243 case 'oci': 244 $this->options['port'] = $this->options['port'] ?? 1521; 245 $this->options['charset'] = $this->options['charset'] ?? 'AL32UTF8'; 246 247 if (!empty($this->options['dsn'])) 248 { 249 $format = 'oci:dbname=#DSN#'; 250 251 $replace = ['#DSN#']; 252 $with = [$this->options['dsn']]; 253 } 254 else 255 { 256 $format = 'oci:dbname=//#HOST#:#PORT#/#DBNAME#'; 257 258 $replace = ['#HOST#', '#PORT#', '#DBNAME#']; 259 $with = [$this->options['host'], $this->options['port'], $this->options['database']]; 260 } 261 262 $format .= ';charset=' . $this->options['charset']; 263 264 break; 265 266 case 'odbc': 267 $format = 'odbc:DSN=#DSN#;UID:#USER#;PWD=#PASSWORD#'; 268 269 $replace = ['#DSN#', '#USER#', '#PASSWORD#']; 270 $with = [$this->options['dsn'], $this->options['user'], $this->options['password']]; 271 272 break; 273 274 case 'pgsql': 275 $this->options['port'] = $this->options['port'] ?? 5432; 276 277 if ($this->options['socket'] !== null) 278 { 279 $format = 'pgsql:host=#SOCKET#;dbname=#DBNAME#'; 280 } 281 else 282 { 283 $format = 'pgsql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 284 } 285 286 $replace = ['#HOST#', '#PORT#', '#SOCKET#', '#DBNAME#']; 287 $with = [$this->options['host'], $this->options['port'], $this->options['socket'], $this->options['database']]; 288 289 // For data in transit TLS encryption. 290 if ($this->options['ssl'] !== [] && $this->options['ssl']['enable'] === true) 291 { 292 if (isset($this->options['ssl']['verify_server_cert']) && $this->options['ssl']['verify_server_cert'] === true) 293 { 294 $format .= ';sslmode=verify-full'; 295 } 296 else 297 { 298 $format .= ';sslmode=require'; 299 } 300 301 $sslKeysMapping = [ 302 'cipher' => null, 303 'ca' => 'sslrootcert', 304 'capath' => null, 305 'key' => 'sslkey', 306 'cert' => 'sslcert', 307 ]; 308 309 // If customised, add cipher suite, ca file path, ca path, private key file path and certificate file path to PDO driver options. 310 foreach ($sslKeysMapping as $key => $value) 311 { 312 if ($value !== null && $this->options['ssl'][$key] !== null) 313 { 314 $format .= ';' . $value . '=' . $this->options['ssl'][$key]; 315 } 316 } 317 } 318 319 break; 320 321 case 'sqlite': 322 if (isset($this->options['version']) && $this->options['version'] == 2) 323 { 324 $format = 'sqlite2:#DBNAME#'; 325 } 326 else 327 { 328 $format = 'sqlite:#DBNAME#'; 329 } 330 331 $replace = ['#DBNAME#']; 332 $with = [$this->options['database']]; 333 334 break; 335 336 case 'sybase': 337 $this->options['port'] = $this->options['port'] ?? 1433; 338 339 $format = 'mssql:host=#HOST#;port=#PORT#;dbname=#DBNAME#'; 340 341 $replace = ['#HOST#', '#PORT#', '#DBNAME#']; 342 $with = [$this->options['host'], $this->options['port'], $this->options['database']]; 343 344 break; 345 346 default: 347 throw new UnsupportedAdapterException('The ' . $this->options['driver'] . ' driver is not supported.'); 348 } 349 350 // Create the connection string: 351 $connectionString = str_replace($replace, $with, $format); 352 353 try 354 { 355 $this->connection = new \PDO( 356 $connectionString, 357 $this->options['user'], 358 $this->options['password'], 359 $this->options['driverOptions'] 360 ); 361 } 362 catch (\PDOException $e) 363 { 364 throw new ConnectionFailureException('Could not connect to PDO: ' . $e->getMessage(), $e->getCode(), $e); 365 } 366 367 $this->setOption(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); 368 369 $this->dispatchEvent(new ConnectionEvent(DatabaseEvents::POST_CONNECT, $this)); 370 } 371 372 /** 373 * Method to escape a string for usage in an SQL statement. 374 * 375 * Oracle escaping reference: 376 * http://www.orafaq.com/wiki/SQL_FAQ#How_does_one_escape_special_characters_when_writing_SQL_queries.3F 377 * 378 * SQLite escaping notes: 379 * http://www.sqlite.org/faq.html#q14 380 * 381 * Method body is as implemented by the Zend Framework 382 * 383 * Note: Using query objects with bound variables is preferable to the below. 384 * 385 * @param string $text The string to be escaped. 386 * @param boolean $extra Unused optional parameter to provide extra escaping. 387 * 388 * @return string The escaped string. 389 * 390 * @since 1.0 391 */ 392 public function escape($text, $extra = false) 393 { 394 if (\is_int($text)) 395 { 396 return $text; 397 } 398 399 if (\is_float($text)) 400 { 401 // Force the dot as a decimal point. 402 return str_replace(',', '.', (string) $text); 403 } 404 405 $text = str_replace("'", "''", (string) $text); 406 407 return addcslashes($text, "\000\n\r\\\032"); 408 } 409 410 /** 411 * Execute the SQL statement. 412 * 413 * @return boolean 414 * 415 * @since 1.0 416 * @throws \Exception 417 * @throws \RuntimeException 418 */ 419 public function execute() 420 { 421 $this->connect(); 422 423 // Take a local copy so that we don't modify the original query and cause issues later 424 $sql = $this->replacePrefix((string) $this->sql); 425 426 // Increment the query counter. 427 $this->count++; 428 429 // Get list of bounded parameters 430 $bounded =& $this->sql->getBounded(); 431 432 // If there is a monitor registered, let it know we are starting this query 433 if ($this->monitor) 434 { 435 $this->monitor->startQuery($sql, $bounded); 436 } 437 438 // Execute the query. 439 $this->executed = false; 440 441 // Bind the variables 442 foreach ($bounded as $key => $obj) 443 { 444 $this->statement->bindParam($key, $obj->value, $obj->dataType, $obj->length, $obj->driverOptions); 445 } 446 447 try 448 { 449 $this->executed = $this->statement->execute(); 450 451 // If there is a monitor registered, let it know we have finished this query 452 if ($this->monitor) 453 { 454 $this->monitor->stopQuery(); 455 } 456 457 return true; 458 } 459 catch (\PDOException $exception) 460 { 461 // If there is a monitor registered, let it know we have finished this query 462 if ($this->monitor) 463 { 464 $this->monitor->stopQuery(); 465 } 466 467 // Get the error number and message before we execute any more queries. 468 $errorNum = (int) $this->statement->errorCode(); 469 $errorMsg = (string) implode(', ', $this->statement->errorInfo()); 470 471 // Check if the server was disconnected. 472 try 473 { 474 if (!$this->connected()) 475 { 476 try 477 { 478 // Attempt to reconnect. 479 $this->connection = null; 480 $this->connect(); 481 } 482 catch (ConnectionFailureException $e) 483 { 484 // If connect fails, ignore that exception and throw the normal exception. 485 throw new ExecutionFailureException($sql, $errorMsg, $errorNum); 486 } 487 488 // Since we were able to reconnect, run the query again. 489 return $this->execute(); 490 } 491 } 492 catch (\LogicException $e) 493 { 494 throw new ExecutionFailureException($sql, $errorMsg, $errorNum, $e); 495 } 496 497 // Throw the normal query exception. 498 throw new ExecutionFailureException($sql, $errorMsg, $errorNum); 499 } 500 } 501 502 /** 503 * Retrieve a PDO database connection attribute 504 * https://www.php.net/manual/en/pdo.getattribute.php 505 * 506 * Usage: $db->getOption(PDO::ATTR_CASE); 507 * 508 * @param mixed $key One of the PDO::ATTR_* Constants 509 * 510 * @return mixed 511 * 512 * @since 1.0 513 */ 514 public function getOption($key) 515 { 516 $this->connect(); 517 518 return $this->connection->getAttribute($key); 519 } 520 521 /** 522 * Get the version of the database connector. 523 * 524 * @return string The database connector version. 525 * 526 * @since 1.5.0 527 */ 528 public function getVersion() 529 { 530 $this->connect(); 531 532 return $this->getOption(\PDO::ATTR_SERVER_VERSION); 533 } 534 535 /** 536 * Get a query to run and verify the database is operational. 537 * 538 * @return string The query to check the health of the DB. 539 * 540 * @since 1.0 541 */ 542 public function getConnectedQuery() 543 { 544 return 'SELECT 1'; 545 } 546 547 /** 548 * Sets an attribute on the PDO database handle. 549 * https://www.php.net/manual/en/pdo.setattribute.php 550 * 551 * Usage: $db->setOption(PDO::ATTR_CASE, PDO::CASE_UPPER); 552 * 553 * @param integer $key One of the PDO::ATTR_* Constants 554 * @param mixed $value One of the associated PDO Constants 555 * related to the particular attribute 556 * key. 557 * 558 * @return boolean 559 * 560 * @since 1.0 561 */ 562 public function setOption($key, $value) 563 { 564 $this->connect(); 565 566 return $this->connection->setAttribute($key, $value); 567 } 568 569 /** 570 * Test to see if the PDO extension is available. 571 * Override as needed to check for specific PDO Drivers. 572 * 573 * @return boolean True on success, false otherwise. 574 * 575 * @since 1.0 576 */ 577 public static function isSupported() 578 { 579 return \defined('\\PDO::ATTR_DRIVER_NAME'); 580 } 581 582 /** 583 * Determines if the connection to the server is active. 584 * 585 * @return boolean True if connected to the database engine. 586 * 587 * @since 1.0 588 * @throws \LogicException 589 */ 590 public function connected() 591 { 592 // Flag to prevent recursion into this function. 593 static $checkingConnected = false; 594 595 if ($checkingConnected) 596 { 597 // Reset this flag and throw an exception. 598 $checkingConnected = false; 599 600 throw new \LogicException('Recursion trying to check if connected.'); 601 } 602 603 // Backup the query state. 604 $sql = $this->sql; 605 $limit = $this->limit; 606 $offset = $this->offset; 607 $statement = $this->statement; 608 609 try 610 { 611 // Set the checking connection flag. 612 $checkingConnected = true; 613 614 // Run a simple query to check the connection. 615 $this->setQuery($this->getConnectedQuery()); 616 $status = (bool) $this->loadResult(); 617 } 618 catch (\Exception $e) 619 { 620 // If we catch an exception here, we must not be connected. 621 $status = false; 622 } 623 624 // Restore the query state. 625 $this->sql = $sql; 626 $this->limit = $limit; 627 $this->offset = $offset; 628 $this->statement = $statement; 629 $checkingConnected = false; 630 631 return $status; 632 } 633 634 /** 635 * Method to get the auto-incremented value from the last INSERT statement. 636 * 637 * @return string The value of the auto-increment field from the last inserted row. 638 * 639 * @since 1.0 640 */ 641 public function insertid() 642 { 643 $this->connect(); 644 645 // Error suppress this to prevent PDO warning us that the driver doesn't support this operation. 646 return @$this->connection->lastInsertId(); 647 } 648 649 /** 650 * Select a database for use. 651 * 652 * @param string $database The name of the database to select for use. 653 * 654 * @return boolean True if the database was successfully selected. 655 * 656 * @since 1.0 657 * @throws \RuntimeException 658 */ 659 public function select($database) 660 { 661 $this->connect(); 662 663 return true; 664 } 665 666 /** 667 * Set the connection to use UTF-8 character encoding. 668 * 669 * @return boolean True on success. 670 * 671 * @since 1.0 672 */ 673 public function setUtf() 674 { 675 return false; 676 } 677 678 /** 679 * Method to commit a transaction. 680 * 681 * @param boolean $toSavepoint If true, commit to the last savepoint. 682 * 683 * @return void 684 * 685 * @since 1.0 686 * @throws \RuntimeException 687 */ 688 public function transactionCommit($toSavepoint = false) 689 { 690 $this->connect(); 691 692 if (!$toSavepoint || $this->transactionDepth === 1) 693 { 694 $this->connection->commit(); 695 } 696 697 $this->transactionDepth--; 698 } 699 700 /** 701 * Method to roll back a transaction. 702 * 703 * @param boolean $toSavepoint If true, rollback to the last savepoint. 704 * 705 * @return void 706 * 707 * @since 1.0 708 * @throws \RuntimeException 709 */ 710 public function transactionRollback($toSavepoint = false) 711 { 712 $this->connect(); 713 714 if (!$toSavepoint || $this->transactionDepth === 1) 715 { 716 $this->connection->rollBack(); 717 } 718 719 $this->transactionDepth--; 720 } 721 722 /** 723 * Method to initialize a transaction. 724 * 725 * @param boolean $asSavepoint If true and a transaction is already active, a savepoint will be created. 726 * 727 * @return void 728 * 729 * @since 1.0 730 * @throws \RuntimeException 731 */ 732 public function transactionStart($asSavepoint = false) 733 { 734 $this->connect(); 735 736 if (!$asSavepoint || !$this->transactionDepth) 737 { 738 $this->connection->beginTransaction(); 739 } 740 741 $this->transactionDepth++; 742 } 743 744 /** 745 * Prepares a SQL statement for execution 746 * 747 * @param string $query The SQL query to be prepared. 748 * 749 * @return StatementInterface 750 * 751 * @since 2.0.0 752 * @throws PrepareStatementFailureException 753 */ 754 protected function prepareStatement(string $query): StatementInterface 755 { 756 try 757 { 758 return new PdoStatement($this->connection->prepare($query, $this->options['driverOptions'])); 759 } 760 catch (\PDOException $exception) 761 { 762 throw new PrepareStatementFailureException($exception->getMessage(), $exception->getCode(), $exception); 763 } 764 } 765 766 /** 767 * PDO does not support serialize 768 * 769 * @return array 770 * 771 * @since 1.0 772 */ 773 public function __sleep() 774 { 775 $serializedProperties = []; 776 777 $reflect = new \ReflectionClass($this); 778 779 // Get properties of the current class 780 $properties = $reflect->getProperties(); 781 782 foreach ($properties as $property) 783 { 784 // Do not serialize properties that are PDO 785 if ($property->isStatic() === false && !($this->{$property->name} instanceof \PDO)) 786 { 787 $serializedProperties[] = $property->name; 788 } 789 } 790 791 return $serializedProperties; 792 } 793 794 /** 795 * Wake up after serialization 796 * 797 * @return void 798 * 799 * @since 1.0 800 */ 801 public function __wakeup() 802 { 803 // Get connection back 804 $this->__construct($this->options); 805 } 806 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Sep 7 05:41:13 2022 | Chilli.vc Blog - For Webmaster,Blog-Writer,System Admin and Domainer |