[ 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; 10 11 use Joomla\Database\Event\ConnectionEvent; 12 use Joomla\Database\Exception\ConnectionFailureException; 13 use Joomla\Database\Exception\ExecutionFailureException; 14 use Joomla\Database\Exception\PrepareStatementFailureException; 15 use Joomla\Event\DispatcherAwareInterface; 16 use Joomla\Event\DispatcherAwareTrait; 17 use Joomla\Event\EventInterface; 18 19 /** 20 * Joomla Framework Database Driver Class 21 * 22 * @since 1.0 23 */ 24 abstract class DatabaseDriver implements DatabaseInterface, DispatcherAwareInterface 25 { 26 use DispatcherAwareTrait; 27 28 /** 29 * The name of the database. 30 * 31 * @var string 32 * @since 1.0 33 */ 34 private $database; 35 36 /** 37 * The name of the database driver. 38 * 39 * @var string 40 * @since 1.0 41 */ 42 protected $name; 43 44 /** 45 * The type of the database server family supported by this driver. 46 * 47 * @var string 48 * @since 1.4.0 49 */ 50 public $serverType; 51 52 /** 53 * The database connection resource. 54 * 55 * @var resource 56 * @since 1.0 57 */ 58 protected $connection; 59 60 /** 61 * Holds the list of available db connectors. 62 * 63 * @var array 64 * @since 1.0 65 */ 66 protected static $connectors = []; 67 68 /** 69 * The number of SQL statements executed by the database driver. 70 * 71 * @var integer 72 * @since 1.0 73 */ 74 protected $count = 0; 75 76 /** 77 * The database connection cursor from the last query. 78 * 79 * @var resource 80 * @since 1.0 81 */ 82 protected $cursor; 83 84 /** 85 * Contains the current query execution status 86 * 87 * @var boolean 88 * @since 2.0.0 89 */ 90 protected $executed = false; 91 92 /** 93 * The affected row limit for the current SQL statement. 94 * 95 * @var integer 96 * @since 1.0 97 */ 98 protected $limit = 0; 99 100 /** 101 * The character(s) used to quote SQL statement names such as table names or field names, etc. 102 * 103 * 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 104 * opening quote and the second for the closing quote. 105 * 106 * @var string 107 * @since 1.0 108 */ 109 protected $nameQuote; 110 111 /** 112 * The null or zero representation of a timestamp for the database driver. 113 * 114 * @var string 115 * @since 1.0 116 */ 117 protected $nullDate; 118 119 /** 120 * The affected row offset to apply for the current SQL statement. 121 * 122 * @var integer 123 * @since 1.0 124 */ 125 protected $offset = 0; 126 127 /** 128 * Passed in upon instantiation and saved. 129 * 130 * @var array 131 * @since 1.0 132 */ 133 protected $options; 134 135 /** 136 * The current SQL statement to execute. 137 * 138 * @var mixed 139 * @since 1.0 140 */ 141 protected $sql; 142 143 /** 144 * The prepared statement. 145 * 146 * @var StatementInterface 147 * @since 2.0.0 148 */ 149 protected $statement; 150 151 /** 152 * The common database table prefix. 153 * 154 * @var string 155 * @since 1.0 156 */ 157 protected $tablePrefix; 158 159 /** 160 * True if the database engine supports UTF-8 character encoding. 161 * 162 * @var boolean 163 * @since 1.0 164 */ 165 protected $utf = true; 166 167 /** 168 * The database error number. 169 * 170 * @var integer 171 * @since 1.0 172 */ 173 protected $errorNum = 0; 174 175 /** 176 * The database error message. 177 * 178 * @var string 179 * @since 1.0 180 */ 181 protected $errorMsg; 182 183 /** 184 * DatabaseDriver instances container. 185 * 186 * @var DatabaseDriver[] 187 * @since 1.0 188 * @deprecated 3.0 Singleton storage will no longer be supported. 189 */ 190 protected static $instances = []; 191 192 /** 193 * The minimum supported database version. 194 * 195 * @var string 196 * @since 1.0 197 */ 198 protected static $dbMinimum; 199 200 /** 201 * The depth of the current transaction. 202 * 203 * @var integer 204 * @since 1.0 205 */ 206 protected $transactionDepth = 0; 207 208 /** 209 * DatabaseFactory object 210 * 211 * @var DatabaseFactory 212 * @since 2.0.0 213 */ 214 protected $factory; 215 216 /** 217 * Query monitor object 218 * 219 * @var QueryMonitorInterface 220 * @since 2.0.0 221 */ 222 protected $monitor; 223 224 /** 225 * Get a list of available database connectors. 226 * 227 * The list will only be populated with connectors that the class exists for and the environment supports its use. 228 * This gives us the ability to have a multitude of connector classes that are self-aware as to whether or not they 229 * are able to be used on a given system. 230 * 231 * @return array An array of available database connectors. 232 * 233 * @since 1.0 234 */ 235 public static function getConnectors() 236 { 237 if (empty(self::$connectors)) 238 { 239 // Get an iterator and loop trough the driver classes. 240 $dir = __DIR__; 241 $iterator = new \DirectoryIterator($dir); 242 243 /** @var $file \DirectoryIterator */ 244 foreach ($iterator as $file) 245 { 246 // Only load for php files. 247 if (!$file->isDir()) 248 { 249 continue; 250 } 251 252 $baseName = $file->getBasename(); 253 254 // Derive the class name from the type. 255 /** @var $class DatabaseDriver */ 256 $class = __NAMESPACE__ . '\\' . ucfirst(strtolower($baseName)) . '\\' . ucfirst(strtolower($baseName)) . 'Driver'; 257 258 // If the class doesn't exist, or if it's not supported on this system, move on to the next type. 259 if (!class_exists($class) || !$class::isSupported()) 260 { 261 continue; 262 } 263 264 // Everything looks good, add it to the list. 265 self::$connectors[] = $baseName; 266 } 267 } 268 269 return self::$connectors; 270 } 271 272 /** 273 * Method to return a DatabaseDriver instance based on the given options. 274 * 275 * There are three global options and then the rest are specific to the database driver. 276 * 277 * - The 'driver' option defines which DatabaseDriver class is used for the connection -- the default is 'mysqli'. 278 * - The 'database' option determines which database is to be used for the connection. 279 * - The 'select' option determines whether the connector should automatically select the chosen database. 280 * 281 * Instances are unique to the given options and new objects are only created when a unique options array is 282 * passed into the method. This ensures that we don't end up with unnecessary database connection resources. 283 * 284 * @param array $options Parameters to be passed to the database driver. 285 * 286 * @return DatabaseDriver 287 * 288 * @since 1.0 289 * @throws \RuntimeException 290 * @deprecated 3.0 Use DatabaseFactory::getDriver() instead 291 */ 292 public static function getInstance(array $options = []) 293 { 294 trigger_deprecation( 295 'joomla/database', 296 '2.0.0', 297 '%s() is deprecated and will be removed in 3.0, use %s::getDriver() instead.', 298 __METHOD__, 299 DatabaseFactory::class 300 ); 301 302 // Sanitize the database connector options. 303 $options['driver'] = isset($options['driver']) ? preg_replace('/[^A-Z0-9_\.-]/i', '', $options['driver']) : 'mysqli'; 304 $options['database'] = $options['database'] ?? null; 305 $options['select'] = $options['select'] ?? true; 306 $options['factory'] = $options['factory'] ?? new DatabaseFactory; 307 $options['monitor'] = $options['monitor'] ?? null; 308 309 // Get the options signature for the database connector. 310 $signature = md5(serialize($options)); 311 312 // If we already have a database connector instance for these options then just use that. 313 if (empty(self::$instances[$signature])) 314 { 315 // Set the new connector to the global instances based on signature. 316 self::$instances[$signature] = $options['factory']->getDriver($options['driver'], $options); 317 } 318 319 return self::$instances[$signature]; 320 } 321 322 /** 323 * Splits a string of multiple queries into an array of individual queries. 324 * 325 * @param string $sql Input SQL string with which to split into individual queries. 326 * 327 * @return array 328 * 329 * @since 1.0 330 */ 331 public static function splitSql($sql) 332 { 333 $start = 0; 334 $open = false; 335 $comment = false; 336 $endString = ''; 337 $end = \strlen($sql); 338 $queries = []; 339 $query = ''; 340 341 for ($i = 0; $i < $end; $i++) 342 { 343 $current = substr($sql, $i, 1); 344 $current2 = substr($sql, $i, 2); 345 $current3 = substr($sql, $i, 3); 346 $lenEndString = \strlen($endString); 347 $testEnd = substr($sql, $i, $lenEndString); 348 349 if ($current === '"' || $current === "'" || $current2 === '--' 350 || ($current2 === '/*' && $current3 !== '/*!' && $current3 !== '/*+') 351 || ($current === '#' && $current3 !== '#__') 352 || ($comment && $testEnd === $endString)) 353 { 354 // Check if quoted with previous backslash 355 $n = 2; 356 357 while (substr($sql, $i - $n + 1, 1) === '\\' && $n < $i) 358 { 359 $n++; 360 } 361 362 // Not quoted 363 if ($n % 2 === 0) 364 { 365 if ($open) 366 { 367 if ($testEnd === $endString) 368 { 369 if ($comment) 370 { 371 $comment = false; 372 373 if ($lenEndString > 1) 374 { 375 $i += ($lenEndString - 1); 376 $current = substr($sql, $i, 1); 377 } 378 379 $start = $i + 1; 380 } 381 382 $open = false; 383 $endString = ''; 384 } 385 } 386 else 387 { 388 $open = true; 389 390 if ($current2 === '--') 391 { 392 $endString = "\n"; 393 $comment = true; 394 } 395 elseif ($current2 === '/*') 396 { 397 $endString = '*/'; 398 $comment = true; 399 } 400 elseif ($current === '#') 401 { 402 $endString = "\n"; 403 $comment = true; 404 } 405 else 406 { 407 $endString = $current; 408 } 409 410 if ($comment && $start < $i) 411 { 412 $query .= substr($sql, $start, $i - $start); 413 } 414 } 415 } 416 } 417 418 if ($comment) 419 { 420 $start = $i + 1; 421 } 422 423 if (($current === ';' && !$open) || $i === $end - 1) 424 { 425 if ($start <= $i) 426 { 427 $query .= substr($sql, $start, $i - $start + 1); 428 } 429 430 $query = trim($query); 431 432 if ($query) 433 { 434 if (($i === $end - 1) && ($current !== ';')) 435 { 436 $query .= ';'; 437 } 438 439 $queries[] = $query; 440 } 441 442 $query = ''; 443 $start = $i + 1; 444 } 445 446 $endComment = false; 447 } 448 449 return $queries; 450 } 451 452 /** 453 * Magic method to access properties of the database driver. 454 * 455 * @param string $name The name of the property. 456 * 457 * @return mixed A value if the property name is valid, null otherwise. 458 * 459 * @since 1.4.0 460 * @deprecated 3.0 This is a B/C proxy since $this->name was previously public 461 */ 462 public function __get($name) 463 { 464 switch ($name) 465 { 466 case 'name': 467 trigger_deprecation( 468 'joomla/database', 469 '1.4.0', 470 'Accessing the name property of %s is deprecated, use the getName() method instead.', 471 self::class 472 ); 473 474 return $this->getName(); 475 476 default: 477 $trace = debug_backtrace(); 478 trigger_error( 479 sprintf( 480 'Undefined property via __get(): %1$s in %2$s on line %3$s', 481 $name, 482 $trace[0]['file'], 483 $trace[0]['line'] 484 ), 485 \E_USER_NOTICE 486 ); 487 } 488 } 489 490 /** 491 * Constructor. 492 * 493 * @param array $options List of options used to configure the connection 494 * 495 * @since 1.0 496 */ 497 public function __construct(array $options) 498 { 499 // Initialise object variables. 500 $this->database = $options['database'] ?? ''; 501 $this->tablePrefix = $options['prefix'] ?? ''; 502 $this->count = 0; 503 $this->errorNum = 0; 504 505 // Set class options. 506 $this->options = $options; 507 508 // Register the DatabaseFactory 509 $this->factory = $options['factory'] ?? new DatabaseFactory; 510 511 // Register the query monitor if available 512 $this->monitor = $options['monitor'] ?? null; 513 } 514 515 /** 516 * Destructor. 517 * 518 * @since 2.0.0 519 */ 520 public function __destruct() 521 { 522 $this->disconnect(); 523 } 524 525 /** 526 * Alter database's character set. 527 * 528 * @param string $dbName The database name that will be altered 529 * 530 * @return boolean|resource 531 * 532 * @since 2.0.0 533 * @throws \RuntimeException 534 */ 535 public function alterDbCharacterSet($dbName) 536 { 537 if ($dbName === null) 538 { 539 throw new \RuntimeException('Database name must not be null.'); 540 } 541 542 $this->setQuery($this->getAlterDbCharacterSet($dbName)); 543 544 return $this->execute(); 545 } 546 547 /** 548 * Create a new database using information from $options object. 549 * 550 * @param \stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. 551 * @param boolean $utf True if the database supports the UTF-8 character set. 552 * 553 * @return boolean|resource 554 * 555 * @since 2.0.0 556 * @throws \RuntimeException 557 */ 558 public function createDatabase($options, $utf = true) 559 { 560 if ($options === null) 561 { 562 throw new \RuntimeException('$options object must not be null.'); 563 } 564 565 if (empty($options->db_name)) 566 { 567 throw new \RuntimeException('$options object must have db_name set.'); 568 } 569 570 if (empty($options->db_user)) 571 { 572 throw new \RuntimeException('$options object must have db_user set.'); 573 } 574 575 $this->setQuery($this->getCreateDatabaseQuery($options, $utf)); 576 577 return $this->execute(); 578 } 579 580 /** 581 * Disconnects the database. 582 * 583 * @return void 584 * 585 * @since 2.0.0 586 */ 587 public function disconnect() 588 { 589 $this->freeResult(); 590 $this->connection = null; 591 592 $this->dispatchEvent(new ConnectionEvent(DatabaseEvents::POST_DISCONNECT, $this)); 593 } 594 595 /** 596 * Dispatch an event. 597 * 598 * @param EventInterface $event The event to dispatch 599 * 600 * @return void 601 * 602 * @since 2.0.0 603 */ 604 protected function dispatchEvent(EventInterface $event) 605 { 606 try 607 { 608 $this->getDispatcher()->dispatch($event->getName(), $event); 609 } 610 catch (\UnexpectedValueException $exception) 611 { 612 // Don't error if a dispatcher hasn't been set 613 } 614 } 615 616 /** 617 * Drops a table from the database. 618 * 619 * @param string $table The name of the database table to drop. 620 * @param boolean $ifExists Optionally specify that the table must exist before it is dropped. 621 * 622 * @return $this 623 * 624 * @since 2.0.0 625 * @throws \RuntimeException 626 */ 627 public function dropTable($table, $ifExists = true) 628 { 629 $this->connect(); 630 631 $this->setQuery('DROP TABLE ' . ($ifExists ? 'IF EXISTS ' : '') . $this->quoteName($table)) 632 ->execute(); 633 634 return $this; 635 } 636 637 /** 638 * Execute the SQL statement. 639 * 640 * @return boolean 641 * 642 * @since 2.0.0 643 * @throws \RuntimeException 644 */ 645 public function execute() 646 { 647 $this->connect(); 648 649 // Increment the query counter. 650 $this->count++; 651 652 // Get list of bound parameters 653 $bounded =& $this->sql->getBounded(); 654 655 // If there is a monitor registered, let it know we are starting this query 656 if ($this->monitor) 657 { 658 // Take a local copy so that we don't modify the original query and cause issues later 659 $sql = $this->replacePrefix((string) $this->sql); 660 661 $this->monitor->startQuery($sql, $bounded); 662 } 663 664 // Execute the query. 665 $this->executed = false; 666 667 // Bind the variables 668 foreach ($bounded as $key => $obj) 669 { 670 $this->statement->bindParam($key, $obj->value, $obj->dataType); 671 } 672 673 try 674 { 675 $this->executed = $this->statement->execute(); 676 677 // If there is a monitor registered, let it know we have finished this query 678 if ($this->monitor) 679 { 680 $this->monitor->stopQuery(); 681 } 682 683 return true; 684 } 685 catch (ExecutionFailureException $exception) 686 { 687 // If there is a monitor registered, let it know we have finished this query 688 if ($this->monitor) 689 { 690 $this->monitor->stopQuery(); 691 } 692 693 // Check if the server was disconnected. 694 if (!$this->connected()) 695 { 696 try 697 { 698 // Attempt to reconnect. 699 $this->connection = null; 700 $this->connect(); 701 } 702 catch (ConnectionFailureException $e) 703 { 704 // If connect fails, ignore that exception and throw the normal exception. 705 throw $exception; 706 } 707 708 // Since we were able to reconnect, run the query again. 709 return $this->execute(); 710 } 711 712 // Throw the normal query exception. 713 throw $exception; 714 } 715 } 716 717 /** 718 * Method to fetch a row from the result set cursor as an array. 719 * 720 * @return mixed Either the next row from the result set or false if there are no more rows. 721 * 722 * @since 1.0 723 */ 724 protected function fetchArray() 725 { 726 if ($this->statement) 727 { 728 return $this->statement->fetch(FetchMode::NUMERIC); 729 } 730 } 731 732 /** 733 * Method to fetch a row from the result set cursor as an associative array. 734 * 735 * @return mixed Either the next row from the result set or false if there are no more rows. 736 * 737 * @since 1.0 738 */ 739 protected function fetchAssoc() 740 { 741 if ($this->statement) 742 { 743 return $this->statement->fetch(FetchMode::ASSOCIATIVE); 744 } 745 } 746 747 /** 748 * Method to fetch a row from the result set cursor as an object. 749 * 750 * Note, the fetch mode should be configured before calling this method using `StatementInterface::setFetchMode()`. 751 * 752 * @return mixed Either the next row from the result set or false if there are no more rows. 753 * 754 * @since 1.0 755 */ 756 protected function fetchObject() 757 { 758 if ($this->statement) 759 { 760 return $this->statement->fetch(); 761 } 762 } 763 764 /** 765 * Method to free up the memory used for the result set. 766 * 767 * @return void 768 * 769 * @since 1.0 770 */ 771 protected function freeResult() 772 { 773 $this->executed = false; 774 775 if ($this->statement) 776 { 777 $this->statement->closeCursor(); 778 } 779 } 780 781 /** 782 * Get the number of affected rows for the previous executed SQL statement. 783 * 784 * @return integer The number of affected rows in the previous operation 785 * 786 * @since 2.0.0 787 */ 788 public function getAffectedRows() 789 { 790 $this->connect(); 791 792 if ($this->statement) 793 { 794 return $this->statement->rowCount(); 795 } 796 797 return 0; 798 } 799 800 /** 801 * Method that provides access to the underlying database connection. 802 * 803 * @return resource The underlying database connection resource. 804 * 805 * @since 1.0 806 */ 807 public function getConnection() 808 { 809 return $this->connection; 810 } 811 812 /** 813 * Get the total number of SQL statements executed by the database driver. 814 * 815 * @return integer 816 * 817 * @since 1.0 818 */ 819 public function getCount() 820 { 821 return $this->count; 822 } 823 824 /** 825 * Return the query string to alter the database character set. 826 * 827 * @param string $dbName The database name 828 * 829 * @return string The query that alter the database query string 830 * 831 * @since 1.6.0 832 */ 833 protected function getAlterDbCharacterSet($dbName) 834 { 835 return 'ALTER DATABASE ' . $this->quoteName($dbName) . ' CHARACTER SET ' . $this->quote('UTF8'); 836 } 837 838 /** 839 * Return the query string to create new Database. 840 * 841 * @param stdClass $options Object used to pass user and database name to database driver. This object must have "db_name" and "db_user" set. 842 * @param boolean $utf True if the database supports the UTF-8 character set. 843 * 844 * @return string The query that creates database 845 * 846 * @since 2.0.0 847 */ 848 protected function getCreateDatabaseQuery($options, $utf) 849 { 850 return 'CREATE DATABASE ' . $this->quoteName($options->db_name); 851 } 852 853 /** 854 * Gets the name of the database used by this connection. 855 * 856 * @return string 857 * 858 * @since 1.0 859 */ 860 protected function getDatabase() 861 { 862 return $this->database; 863 } 864 865 /** 866 * Returns a PHP date() function compliant date format for the database driver. 867 * 868 * @return string 869 * 870 * @since 1.0 871 */ 872 public function getDateFormat() 873 { 874 return 'Y-m-d H:i:s'; 875 } 876 877 /** 878 * Get the minimum supported database version. 879 * 880 * @return string 881 * 882 * @since 1.0 883 */ 884 public function getMinimum() 885 { 886 return static::$dbMinimum; 887 } 888 889 /** 890 * Get the name of the database driver. 891 * 892 * If $this->name is not set it will try guessing the driver name from the class name. 893 * 894 * @return string 895 * 896 * @since 1.4.0 897 */ 898 public function getName() 899 { 900 if (empty($this->name)) 901 { 902 $reflect = new \ReflectionClass($this); 903 904 $this->name = strtolower(str_replace('Driver', '', $reflect->getShortName())); 905 } 906 907 return $this->name; 908 } 909 910 /** 911 * Get the number of returned rows for the previous executed SQL statement. 912 * 913 * @return integer The number of returned rows. 914 * 915 * @since 2.0.0 916 */ 917 public function getNumRows() 918 { 919 $this->connect(); 920 921 if ($this->statement) 922 { 923 return $this->statement->rowCount(); 924 } 925 926 return 0; 927 } 928 929 /** 930 * Get the server family type. 931 * 932 * If $this->serverType is not set it will attempt guessing the server family type from the driver name. If this is not possible the driver 933 * name will be returned instead. 934 * 935 * @return string 936 * 937 * @since 1.4.0 938 */ 939 public function getServerType() 940 { 941 if (empty($this->serverType)) 942 { 943 $name = $this->getName(); 944 945 if (stristr($name, 'mysql') !== false) 946 { 947 $this->serverType = 'mysql'; 948 } 949 elseif (stristr($name, 'postgre') !== false) 950 { 951 $this->serverType = 'postgresql'; 952 } 953 elseif (stristr($name, 'pgsql') !== false) 954 { 955 $this->serverType = 'postgresql'; 956 } 957 elseif (stristr($name, 'oracle') !== false) 958 { 959 $this->serverType = 'oracle'; 960 } 961 elseif (stristr($name, 'sqlite') !== false) 962 { 963 $this->serverType = 'sqlite'; 964 } 965 elseif (stristr($name, 'sqlsrv') !== false) 966 { 967 $this->serverType = 'mssql'; 968 } 969 elseif (stristr($name, 'sqlazure') !== false) 970 { 971 $this->serverType = 'mssql'; 972 } 973 elseif (stristr($name, 'mssql') !== false) 974 { 975 $this->serverType = 'mssql'; 976 } 977 else 978 { 979 $this->serverType = $name; 980 } 981 } 982 983 return $this->serverType; 984 } 985 986 /** 987 * Get the null or zero representation of a timestamp for the database driver. 988 * 989 * @return string 990 * 991 * @since 1.0 992 */ 993 public function getNullDate() 994 { 995 return $this->nullDate; 996 } 997 998 /** 999 * Get the common table prefix for the database driver. 1000 * 1001 * @return string The common database table prefix. 1002 * 1003 * @since 1.0 1004 */ 1005 public function getPrefix() 1006 { 1007 return $this->tablePrefix; 1008 } 1009 1010 /** 1011 * Gets an exporter class object. 1012 * 1013 * @return DatabaseExporter An exporter object. 1014 * 1015 * @since 1.0 1016 * @throws \RuntimeException 1017 */ 1018 public function getExporter() 1019 { 1020 return $this->factory->getExporter($this->name, $this); 1021 } 1022 1023 /** 1024 * Gets an importer class object. 1025 * 1026 * @return DatabaseImporter 1027 * 1028 * @since 1.0 1029 */ 1030 public function getImporter() 1031 { 1032 return $this->factory->getImporter($this->name, $this); 1033 } 1034 1035 /** 1036 * Get the current query object or a new DatabaseQuery object. 1037 * 1038 * @param boolean $new False to return the current query object, True to return a new DatabaseQuery object. 1039 * 1040 * @return DatabaseQuery 1041 * 1042 * @since 1.0 1043 */ 1044 public function getQuery($new = false) 1045 { 1046 if ($new) 1047 { 1048 return $this->factory->getQuery($this->name, $this); 1049 } 1050 1051 return $this->sql; 1052 } 1053 1054 /** 1055 * Get a new iterator on the current query. 1056 * 1057 * @param string $column An option column to use as the iterator key. 1058 * @param string $class The class of object that is returned. 1059 * 1060 * @return DatabaseIterator 1061 * 1062 * @since 1.0 1063 */ 1064 public function getIterator($column = null, $class = \stdClass::class) 1065 { 1066 if (!$this->executed) 1067 { 1068 $this->execute(); 1069 } 1070 1071 /** 1072 * Calling setQuery free's the statement from the iterator which will break the iterator. 1073 * So we set statement to null so that freeResult on the statement here has no affect. 1074 * If you unset the iterator object then that will close the cursor and free the result. 1075 */ 1076 $iterator = $this->factory->getIterator($this->name, $this->statement, $column, $class); 1077 $this->statement = null; 1078 1079 return $iterator; 1080 } 1081 1082 /** 1083 * Shows the table CREATE statement that creates the given tables. 1084 * 1085 * @param mixed $tables A table name or a list of table names. 1086 * 1087 * @return array A list of the create SQL for the tables. 1088 * 1089 * @since 1.0 1090 * @throws \RuntimeException 1091 */ 1092 abstract public function getTableCreate($tables); 1093 1094 /** 1095 * Determine whether or not the database engine supports UTF-8 character encoding. 1096 * 1097 * @return boolean True if the database engine supports UTF-8 character encoding. 1098 * 1099 * @since 1.0 1100 */ 1101 public function hasUtfSupport() 1102 { 1103 return $this->utf; 1104 } 1105 1106 /** 1107 * Inserts a row into a table based on an object's properties. 1108 * 1109 * @param string $table The name of the database table to insert into. 1110 * @param object $object A reference to an object whose public properties match the table fields. 1111 * @param string $key The name of the primary key. If provided the object property is updated. 1112 * 1113 * @return boolean 1114 * 1115 * @since 1.0 1116 * @throws \RuntimeException 1117 */ 1118 public function insertObject($table, &$object, $key = null) 1119 { 1120 $fields = []; 1121 $values = []; 1122 $tableColumns = $this->getTableColumns($table); 1123 1124 // Iterate over the object variables to build the query fields and values. 1125 foreach (get_object_vars($object) as $k => $v) 1126 { 1127 // Skip columns that don't exist in the table. 1128 if (!\array_key_exists($k, $tableColumns)) 1129 { 1130 continue; 1131 } 1132 1133 // Only process non-null scalars. 1134 if (\is_array($v) || \is_object($v) || $v === null) 1135 { 1136 continue; 1137 } 1138 1139 // Ignore any internal fields. 1140 if ($k[0] === '_') 1141 { 1142 continue; 1143 } 1144 1145 // Prepare and sanitize the fields and values for the database query. 1146 $fields[] = $this->quoteName($k); 1147 $values[] = $this->quote($v); 1148 } 1149 1150 // Create the base insert statement. 1151 $query = $this->getQuery(true) 1152 ->insert($this->quoteName($table)) 1153 ->columns($fields) 1154 ->values(implode(',', $values)); 1155 1156 // Set the query and execute the insert. 1157 $this->setQuery($query)->execute(); 1158 1159 // Update the primary key if it exists. 1160 $id = $this->insertid(); 1161 1162 if ($key && $id && \is_string($key)) 1163 { 1164 $object->$key = $id; 1165 } 1166 1167 return true; 1168 } 1169 1170 /** 1171 * Method to check whether the installed database version is supported by the database driver 1172 * 1173 * @return boolean True if the database version is supported 1174 * 1175 * @since 1.0 1176 */ 1177 public function isMinimumVersion() 1178 { 1179 return version_compare($this->getVersion(), $this->getMinimum()) >= 0; 1180 } 1181 1182 /** 1183 * Method to get the first row of the result set from the database query as an associative array of ['field_name' => 'row_value']. 1184 * 1185 * @return mixed The return value or null if the query failed. 1186 * 1187 * @since 1.0 1188 * @throws \RuntimeException 1189 */ 1190 public function loadAssoc() 1191 { 1192 $this->connect(); 1193 1194 $ret = null; 1195 1196 // Execute the query and get the result set cursor. 1197 $this->execute(); 1198 1199 // Get the first row from the result set as an associative array. 1200 $array = $this->fetchAssoc(); 1201 1202 if ($array) 1203 { 1204 $ret = $array; 1205 } 1206 1207 // Free up system resources and return. 1208 $this->freeResult(); 1209 1210 return $ret; 1211 } 1212 1213 /** 1214 * Method to get an array of the result set rows from the database query where each row is an associative array 1215 * of ['field_name' => 'row_value']. The array of rows can optionally be keyed by a field name, but defaults to 1216 * a sequential numeric array. 1217 * 1218 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted 1219 * behavior and should be avoided. 1220 * 1221 * @param string $key The name of a field on which to key the result array. 1222 * @param string $column An optional column name. Instead of the whole row, only this column value will be in 1223 * the result array. 1224 * 1225 * @return mixed The return value or null if the query failed. 1226 * 1227 * @since 1.0 1228 * @throws \RuntimeException 1229 */ 1230 public function loadAssocList($key = null, $column = null) 1231 { 1232 $this->connect(); 1233 1234 $array = []; 1235 1236 // Execute the query and get the result set cursor. 1237 $this->execute(); 1238 1239 // Get all of the rows from the result set. 1240 while ($row = $this->fetchAssoc()) 1241 { 1242 $value = $column ? ($row[$column] ?? $row) : $row; 1243 1244 if ($key) 1245 { 1246 $array[$row[$key]] = $value; 1247 } 1248 else 1249 { 1250 $array[] = $value; 1251 } 1252 } 1253 1254 // Free up system resources and return. 1255 $this->freeResult(); 1256 1257 return $array; 1258 } 1259 1260 /** 1261 * Method to get an array of values from the <var>$offset</var> field in each row of the result set from the database query. 1262 * 1263 * @param integer $offset The row offset to use to build the result array. 1264 * 1265 * @return mixed The return value or null if the query failed. 1266 * 1267 * @since 1.0 1268 * @throws \RuntimeException 1269 */ 1270 public function loadColumn($offset = 0) 1271 { 1272 $this->connect(); 1273 1274 $array = []; 1275 1276 // Execute the query and get the result set cursor. 1277 $this->execute(); 1278 1279 // Get all of the rows from the result set as arrays. 1280 while ($row = $this->fetchArray()) 1281 { 1282 $array[] = $row[$offset]; 1283 } 1284 1285 // Free up system resources and return. 1286 $this->freeResult(); 1287 1288 return $array; 1289 } 1290 1291 /** 1292 * Method to get the first row of the result set from the database query as an object. 1293 * 1294 * @param string $class The class name to use for the returned row object. 1295 * 1296 * @return mixed The return value or null if the query failed. 1297 * 1298 * @since 1.0 1299 * @throws \RuntimeException 1300 */ 1301 public function loadObject($class = \stdClass::class) 1302 { 1303 $this->connect(); 1304 1305 $ret = null; 1306 1307 if ($this->statement) 1308 { 1309 $fetchMode = $class === \stdClass::class ? FetchMode::STANDARD_OBJECT : FetchMode::CUSTOM_OBJECT; 1310 1311 // PDO doesn't allow extra arguments for \PDO::FETCH_CLASS, so only forward the class for the custom object mode 1312 if ($fetchMode === FetchMode::STANDARD_OBJECT) 1313 { 1314 $this->statement->setFetchMode($fetchMode); 1315 } 1316 else 1317 { 1318 $this->statement->setFetchMode($fetchMode, $class); 1319 } 1320 } 1321 1322 // Execute the query and get the result set cursor. 1323 $this->execute(); 1324 1325 // Get the first row from the result set as an object of type $class. 1326 $object = $this->fetchObject(); 1327 1328 if ($object) 1329 { 1330 $ret = $object; 1331 } 1332 1333 // Free up system resources and return. 1334 $this->freeResult(); 1335 1336 return $ret; 1337 } 1338 1339 /** 1340 * Method to get an array of the result set rows from the database query where each row is an object. The array 1341 * of objects can optionally be keyed by a field name, but defaults to a sequential numeric array. 1342 * 1343 * NOTE: Choosing to key the result array by a non-unique field name can result in unwanted behavior and should be avoided. 1344 * 1345 * @param string $key The name of a field on which to key the result array. 1346 * @param string $class The class name to use for the returned row objects. 1347 * 1348 * @return mixed The return value or null if the query failed. 1349 * 1350 * @since 1.0 1351 * @throws \RuntimeException 1352 */ 1353 public function loadObjectList($key = '', $class = \stdClass::class) 1354 { 1355 $this->connect(); 1356 1357 $array = []; 1358 1359 if ($this->statement) 1360 { 1361 $fetchMode = $class === \stdClass::class ? FetchMode::STANDARD_OBJECT : FetchMode::CUSTOM_OBJECT; 1362 1363 // PDO doesn't allow extra arguments for \PDO::FETCH_CLASS, so only forward the class for the custom object mode 1364 if ($fetchMode === FetchMode::STANDARD_OBJECT) 1365 { 1366 $this->statement->setFetchMode($fetchMode); 1367 } 1368 else 1369 { 1370 $this->statement->setFetchMode($fetchMode, $class); 1371 } 1372 } 1373 1374 // Execute the query and get the result set cursor. 1375 $this->execute(); 1376 1377 // Get all of the rows from the result set as objects of type $class. 1378 while ($row = $this->fetchObject()) 1379 { 1380 if ($key) 1381 { 1382 $array[$row->$key] = $row; 1383 } 1384 else 1385 { 1386 $array[] = $row; 1387 } 1388 } 1389 1390 // Free up system resources and return. 1391 $this->freeResult(); 1392 1393 return $array; 1394 } 1395 1396 /** 1397 * Method to get the first field of the first row of the result set from the database query. 1398 * 1399 * @return mixed The return value or null if the query failed. 1400 * 1401 * @since 1.0 1402 * @throws \RuntimeException 1403 */ 1404 public function loadResult() 1405 { 1406 $this->connect(); 1407 1408 $ret = null; 1409 1410 // Execute the query and get the result set cursor. 1411 $this->execute(); 1412 1413 // Get the first row from the result set as an array. 1414 $row = $this->fetchArray(); 1415 1416 if ($row) 1417 { 1418 $ret = $row[0]; 1419 } 1420 1421 // Free up system resources and return. 1422 $this->freeResult(); 1423 1424 return $ret; 1425 } 1426 1427 /** 1428 * Method to get the first row of the result set from the database query as an array. 1429 * 1430 * Columns are indexed numerically so the first column in the result set would be accessible via <var>$row[0]</var>, etc. 1431 * 1432 * @return mixed The return value or null if the query failed. 1433 * 1434 * @since 1.0 1435 * @throws \RuntimeException 1436 */ 1437 public function loadRow() 1438 { 1439 $this->connect(); 1440 1441 $ret = null; 1442 1443 // Execute the query and get the result set cursor. 1444 $this->execute(); 1445 1446 // Get the first row from the result set as an array. 1447 $row = $this->fetchArray(); 1448 1449 if ($row) 1450 { 1451 $ret = $row; 1452 } 1453 1454 // Free up system resources and return. 1455 $this->freeResult(); 1456 1457 return $ret; 1458 } 1459 1460 /** 1461 * Method to get an array of the result set rows from the database query where each row is an array. The array 1462 * of objects can optionally be keyed by a field offset, but defaults to a sequential numeric array. 1463 * 1464 * NOTE: Choosing to key the result array by a non-unique field can result in unwanted behavior and should be avoided. 1465 * 1466 * @param string $key The name of a field on which to key the result array. 1467 * 1468 * @return array An array of results. 1469 * 1470 * @since 1.0 1471 * @throws \RuntimeException 1472 */ 1473 public function loadRowList($key = null) 1474 { 1475 $this->connect(); 1476 1477 $array = []; 1478 1479 // Execute the query and get the result set cursor. 1480 $this->execute(); 1481 1482 // Get all of the rows from the result set as arrays. 1483 while ($row = $this->fetchArray()) 1484 { 1485 if ($key !== null) 1486 { 1487 $array[$row[$key]] = $row; 1488 } 1489 else 1490 { 1491 $array[] = $row; 1492 } 1493 } 1494 1495 // Free up system resources and return. 1496 $this->freeResult(); 1497 1498 return $array; 1499 } 1500 1501 /** 1502 * Prepares a SQL statement for execution 1503 * 1504 * @param string $query The SQL query to be prepared. 1505 * 1506 * @return StatementInterface 1507 * 1508 * @since 2.0.0 1509 * @throws PrepareStatementFailureException 1510 */ 1511 abstract protected function prepareStatement(string $query): StatementInterface; 1512 1513 /** 1514 * Alias for quote method 1515 * 1516 * @param array|string $text A string or an array of strings to quote. 1517 * @param boolean $escape True (default) to escape the string, false to leave it unchanged. 1518 * 1519 * @return string The quoted input string. 1520 * 1521 * @since 1.0 1522 */ 1523 public function q($text, $escape = true) 1524 { 1525 return $this->quote($text, $escape); 1526 } 1527 1528 /** 1529 * Quotes and optionally escapes a string to database requirements for use in database queries. 1530 * 1531 * @param array|string $text A string or an array of strings to quote. 1532 * @param boolean $escape True (default) to escape the string, false to leave it unchanged. 1533 * 1534 * @return string The quoted input string. 1535 * 1536 * @since 1.0 1537 */ 1538 public function quote($text, $escape = true) 1539 { 1540 if (\is_array($text)) 1541 { 1542 foreach ($text as $k => $v) 1543 { 1544 $text[$k] = $this->quote($v, $escape); 1545 } 1546 1547 return $text; 1548 } 1549 1550 return '\'' . ($escape ? $this->escape($text) : $text) . '\''; 1551 } 1552 1553 /** 1554 * Quotes a binary string to database requirements for use in database queries. 1555 * 1556 * @param string $data A binary string to quote. 1557 * 1558 * @return string The binary quoted input string. 1559 * 1560 * @since 1.7.0 1561 */ 1562 public function quoteBinary($data) 1563 { 1564 // SQL standard syntax for hexadecimal literals 1565 return "X'" . bin2hex($data) . "'"; 1566 } 1567 1568 /** 1569 * Replace special placeholder representing binary field with the original string. 1570 * 1571 * @param string|resource $data Encoded string or resource. 1572 * 1573 * @return string The original string. 1574 * 1575 * @since 1.7.0 1576 */ 1577 public function decodeBinary($data) 1578 { 1579 return $data; 1580 } 1581 1582 /** 1583 * Alias for quoteName method 1584 * 1585 * @param array|string $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. 1586 * Each type supports dot-notation name. 1587 * @param array|string $as The AS query part associated to $name. It can be string or array, in latter case it has to be 1588 * same length of $name; if is null there will not be any AS part for string or array element. 1589 * 1590 * @return array|string The quote wrapped name, same type of $name. 1591 * 1592 * @since 1.0 1593 */ 1594 public function qn($name, $as = null) 1595 { 1596 return $this->quoteName($name, $as); 1597 } 1598 1599 /** 1600 * Wrap an SQL statement identifier name such as column, table or database names in quotes to prevent injection 1601 * risks and reserved word conflicts. 1602 * 1603 * @param array|string $name The identifier name to wrap in quotes, or an array of identifier names to wrap in quotes. 1604 * Each type supports dot-notation name. 1605 * @param array|string $as The AS query part associated to $name. It can be string or array, in latter case it has to be 1606 * same length of $name; if is null there will not be any AS part for string or array element. 1607 * 1608 * @return array|string The quote wrapped name, same type of $name. 1609 * 1610 * @since 1.0 1611 */ 1612 public function quoteName($name, $as = null) 1613 { 1614 if (\is_string($name)) 1615 { 1616 $name = $this->quoteNameString($name); 1617 1618 if ($as !== null) 1619 { 1620 $name .= ' AS ' . $this->quoteNameString($as, true); 1621 } 1622 1623 return $name; 1624 } 1625 1626 $fin = []; 1627 1628 if ($as === null) 1629 { 1630 foreach ($name as $str) 1631 { 1632 $fin[] = $this->quoteName($str); 1633 } 1634 } 1635 elseif (\is_array($name) && (\count($name) === \count($as))) 1636 { 1637 $count = \count($name); 1638 1639 for ($i = 0; $i < $count; $i++) 1640 { 1641 $fin[] = $this->quoteName($name[$i], $as[$i]); 1642 } 1643 } 1644 1645 return $fin; 1646 } 1647 1648 /** 1649 * Quote string coming from quoteName call. 1650 * 1651 * @param string $name Identifier name to be quoted. 1652 * @param boolean $asSinglePart Treat the name as a single part of the identifier. 1653 * 1654 * @return string Quoted identifier string. 1655 * 1656 * @since 1.7.0 1657 */ 1658 protected function quoteNameString($name, $asSinglePart = false) 1659 { 1660 $q = $this->nameQuote . $this->nameQuote; 1661 1662 // Double quote reserved keyword 1663 $name = str_replace($q[1], $q[1] . $q[1], $name); 1664 1665 if ($asSinglePart) 1666 { 1667 return $q[0] . $name . $q[1]; 1668 } 1669 1670 return $q[0] . str_replace('.', "$q[1].$q[0]", $name) . $q[1]; 1671 } 1672 1673 /** 1674 * Quote strings coming from quoteName call. 1675 * 1676 * @param array $strArr Array of strings coming from quoteName dot-explosion. 1677 * 1678 * @return string Dot-imploded string of quoted parts. 1679 * 1680 * @since 1.0 1681 * @deprecated 2.0 Use quoteNameString instead 1682 */ 1683 protected function quoteNameStr($strArr) 1684 { 1685 $parts = []; 1686 $q = $this->nameQuote; 1687 1688 foreach ($strArr as $part) 1689 { 1690 if ($part === null) 1691 { 1692 continue; 1693 } 1694 1695 if (\strlen($q) === 1) 1696 { 1697 $parts[] = $q . $part . $q; 1698 } 1699 else 1700 { 1701 $parts[] = $q[0] . $part . $q[1]; 1702 } 1703 } 1704 1705 return implode('.', $parts); 1706 } 1707 1708 /** 1709 * This function replaces a string identifier with the configured table prefix. 1710 * 1711 * @param string $sql The SQL statement to prepare. 1712 * @param string $prefix The table prefix. 1713 * 1714 * @return string The processed SQL statement. 1715 * 1716 * @since 1.0 1717 */ 1718 public function replacePrefix($sql, $prefix = '#__') 1719 { 1720 $escaped = false; 1721 $startPos = 0; 1722 $quoteChar = ''; 1723 $literal = ''; 1724 1725 $sql = trim($sql); 1726 $n = \strlen($sql); 1727 1728 while ($startPos < $n) 1729 { 1730 $ip = strpos($sql, $prefix, $startPos); 1731 1732 if ($ip === false) 1733 { 1734 break; 1735 } 1736 1737 $j = strpos($sql, "'", $startPos); 1738 $k = strpos($sql, '"', $startPos); 1739 1740 if (($k !== false) && (($k < $j) || ($j === false))) 1741 { 1742 $quoteChar = '"'; 1743 $j = $k; 1744 } 1745 else 1746 { 1747 $quoteChar = "'"; 1748 } 1749 1750 if ($j === false) 1751 { 1752 $j = $n; 1753 } 1754 1755 $literal .= str_replace($prefix, $this->tablePrefix, substr($sql, $startPos, $j - $startPos)); 1756 $startPos = $j; 1757 1758 $j = $startPos + 1; 1759 1760 if ($j >= $n) 1761 { 1762 break; 1763 } 1764 1765 // Quote comes first, find end of quote 1766 while (true) 1767 { 1768 $k = strpos($sql, $quoteChar, $j); 1769 $escaped = false; 1770 1771 if ($k === false) 1772 { 1773 break; 1774 } 1775 1776 $l = $k - 1; 1777 1778 while ($l >= 0 && $sql[$l] === '\\') 1779 { 1780 $l--; 1781 $escaped = !$escaped; 1782 } 1783 1784 if ($escaped) 1785 { 1786 $j = $k + 1; 1787 1788 continue; 1789 } 1790 1791 break; 1792 } 1793 1794 if ($k === false) 1795 { 1796 // Error in the query - no end quote; ignore it 1797 break; 1798 } 1799 1800 $literal .= substr($sql, $startPos, $k - $startPos + 1); 1801 $startPos = $k + 1; 1802 } 1803 1804 if ($startPos < $n) 1805 { 1806 $literal .= substr($sql, $startPos, $n - $startPos); 1807 } 1808 1809 return $literal; 1810 } 1811 1812 /** 1813 * Get the query monitor. 1814 * 1815 * @return QueryMonitorInterface|null The query monitor or null if not set. 1816 * 1817 * @since 2.0.0 1818 */ 1819 public function getMonitor() 1820 { 1821 return $this->monitor; 1822 } 1823 1824 /** 1825 * Set a query monitor. 1826 * 1827 * @param QueryMonitorInterface|null $monitor The query monitor. 1828 * 1829 * @return $this 1830 * 1831 * @since 2.0.0 1832 */ 1833 public function setMonitor(QueryMonitorInterface $monitor = null) 1834 { 1835 $this->monitor = $monitor; 1836 1837 return $this; 1838 } 1839 1840 /** 1841 * Sets the SQL statement string for later execution. 1842 * 1843 * @param string|QueryInterface $query The SQL statement to set either as a Query object or a string. 1844 * @param integer $offset The affected row offset to set. {@deprecated 3.0 Use LimitableInterface::setLimit() instead} 1845 * @param integer $limit The maximum affected rows to set. {@deprecated 3.0 Use LimitableInterface::setLimit() instead} 1846 * 1847 * @return $this 1848 * 1849 * @since 1.0 1850 * @throws \InvalidArgumentException 1851 */ 1852 public function setQuery($query, $offset = 0, $limit = 0) 1853 { 1854 $this->connect(); 1855 1856 $this->freeResult(); 1857 1858 if (\is_string($query)) 1859 { 1860 // Allows taking advantage of bound variables in a direct query: 1861 $query = $this->getQuery(true)->setQuery($query); 1862 } 1863 elseif (!($query instanceof QueryInterface)) 1864 { 1865 throw new \InvalidArgumentException( 1866 sprintf( 1867 'A query must be a string or a %s instance, a %s was given.', 1868 QueryInterface::class, 1869 \gettype($query) === 'object' ? (\get_class($query) . ' instance') : \gettype($query) 1870 ) 1871 ); 1872 } 1873 1874 if ($offset > 0 || $limit > 0) 1875 { 1876 trigger_deprecation( 1877 'joomla/database', 1878 '2.0.0', 1879 'The "$offset" and "$limit" arguments of %s() are deprecated and will be removed in 3.0, use %s::setLimit() instead.', 1880 __METHOD__, 1881 QueryInterface::class 1882 ); 1883 } 1884 1885 // Check for values set on the query object and use those if there is a zero value passed here 1886 if ($limit === 0 && $query->limit > 0) 1887 { 1888 $limit = $query->limit; 1889 } 1890 1891 if ($offset === 0 && $query->offset > 0) 1892 { 1893 $offset = $query->offset; 1894 } 1895 1896 $query->setLimit($limit, $offset); 1897 1898 $sql = $this->replacePrefix((string) $query); 1899 1900 $this->statement = $this->prepareStatement($sql); 1901 1902 $this->sql = $query; 1903 $this->limit = (int) max(0, $limit); 1904 $this->offset = (int) max(0, $offset); 1905 1906 return $this; 1907 } 1908 1909 /** 1910 * Set the connection to use UTF-8 character encoding. 1911 * 1912 * @return boolean True on success. 1913 * 1914 * @since 1.0 1915 */ 1916 abstract public function setUtf(); 1917 1918 /** 1919 * Method to truncate a table. 1920 * 1921 * @param string $table The table to truncate 1922 * 1923 * @return void 1924 * 1925 * @since 1.0 1926 * @throws \RuntimeException 1927 */ 1928 public function truncateTable($table) 1929 { 1930 $this->setQuery('TRUNCATE TABLE ' . $this->quoteName($table)) 1931 ->execute(); 1932 } 1933 1934 /** 1935 * Updates a row in a table based on an object's properties. 1936 * 1937 * @param string $table The name of the database table to update. 1938 * @param object $object A reference to an object whose public properties match the table fields. 1939 * @param array|string $key The name of the primary key. 1940 * @param boolean $nulls True to update null fields or false to ignore them. 1941 * 1942 * @return boolean True on success. 1943 * 1944 * @since 1.0 1945 * @throws \RuntimeException 1946 */ 1947 public function updateObject($table, &$object, $key, $nulls = false) 1948 { 1949 $fields = []; 1950 $where = []; 1951 $tableColumns = $this->getTableColumns($table); 1952 1953 if (\is_string($key)) 1954 { 1955 $key = [$key]; 1956 } 1957 1958 if (\is_object($key)) 1959 { 1960 $key = (array) $key; 1961 } 1962 1963 // Create the base update statement. 1964 $statement = 'UPDATE ' . $this->quoteName($table) . ' SET %s WHERE %s'; 1965 1966 // Iterate over the object variables to build the query fields/value pairs. 1967 foreach (get_object_vars($object) as $k => $v) 1968 { 1969 // Skip columns that don't exist in the table. 1970 if (!\array_key_exists($k, $tableColumns)) 1971 { 1972 continue; 1973 } 1974 1975 // Only process scalars that are not internal fields. 1976 if (\is_array($v) || \is_object($v) || $k[0] === '_') 1977 { 1978 continue; 1979 } 1980 1981 // Set the primary key to the WHERE clause instead of a field to update. 1982 if (\in_array($k, $key, true)) 1983 { 1984 $where[] = $this->quoteName($k) . ($v === null ? ' IS NULL' : ' = ' . $this->quote($v)); 1985 1986 continue; 1987 } 1988 1989 // Prepare and sanitize the fields and values for the database query. 1990 if ($v === null) 1991 { 1992 // If the value is null and we want to update nulls then set it. 1993 if ($nulls) 1994 { 1995 $val = 'NULL'; 1996 } 1997 else 1998 { 1999 // If the value is null and we do not want to update nulls then ignore this field. 2000 continue; 2001 } 2002 } 2003 else 2004 { 2005 // The field is not null so we prep it for update. 2006 $val = $this->quote($v); 2007 } 2008 2009 // Add the field to be updated. 2010 $fields[] = $this->quoteName($k) . '=' . $val; 2011 } 2012 2013 // We don't have any fields to update. 2014 if (empty($fields)) 2015 { 2016 return true; 2017 } 2018 2019 // Set the query and execute the update. 2020 $this->setQuery(sprintf($statement, implode(',', $fields), implode(' AND ', $where)))->execute(); 2021 2022 return true; 2023 } 2024 }
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 |