annotate core/lib/Drupal/Core/Database/Database.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents af1871eacc83
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\Database;
Chris@0 4
Chris@0 5 /**
Chris@0 6 * Primary front-controller for the database system.
Chris@0 7 *
Chris@0 8 * This class is uninstantiatable and un-extendable. It acts to encapsulate
Chris@0 9 * all control and shepherding of database connections into a single location
Chris@0 10 * without the use of globals.
Chris@0 11 */
Chris@0 12 abstract class Database {
Chris@0 13
Chris@0 14 /**
Chris@0 15 * Flag to indicate a query call should simply return NULL.
Chris@0 16 *
Chris@0 17 * This is used for queries that have no reasonable return value anyway, such
Chris@0 18 * as INSERT statements to a table without a serial primary key.
Chris@0 19 */
Chris@0 20 const RETURN_NULL = 0;
Chris@0 21
Chris@0 22 /**
Chris@0 23 * Flag to indicate a query call should return the prepared statement.
Chris@0 24 */
Chris@0 25 const RETURN_STATEMENT = 1;
Chris@0 26
Chris@0 27 /**
Chris@0 28 * Flag to indicate a query call should return the number of affected rows.
Chris@0 29 */
Chris@0 30 const RETURN_AFFECTED = 2;
Chris@0 31
Chris@0 32 /**
Chris@0 33 * Flag to indicate a query call should return the "last insert id".
Chris@0 34 */
Chris@0 35 const RETURN_INSERT_ID = 3;
Chris@0 36
Chris@0 37 /**
Chris@0 38 * An nested array of all active connections. It is keyed by database name
Chris@0 39 * and target.
Chris@0 40 *
Chris@0 41 * @var array
Chris@0 42 */
Chris@18 43 protected static $connections = [];
Chris@0 44
Chris@0 45 /**
Chris@0 46 * A processed copy of the database connection information from settings.php.
Chris@0 47 *
Chris@0 48 * @var array
Chris@0 49 */
Chris@18 50 protected static $databaseInfo = [];
Chris@0 51
Chris@0 52 /**
Chris@0 53 * A list of key/target credentials to simply ignore.
Chris@0 54 *
Chris@0 55 * @var array
Chris@0 56 */
Chris@18 57 protected static $ignoreTargets = [];
Chris@0 58
Chris@0 59 /**
Chris@0 60 * The key of the currently active database connection.
Chris@0 61 *
Chris@0 62 * @var string
Chris@0 63 */
Chris@18 64 protected static $activeKey = 'default';
Chris@0 65
Chris@0 66 /**
Chris@0 67 * An array of active query log objects.
Chris@0 68 *
Chris@0 69 * Every connection has one and only one logger object for all targets and
Chris@0 70 * logging keys.
Chris@0 71 *
Chris@0 72 * array(
Chris@0 73 * '$db_key' => DatabaseLog object.
Chris@0 74 * );
Chris@0 75 *
Chris@0 76 * @var array
Chris@0 77 */
Chris@18 78 protected static $logs = [];
Chris@0 79
Chris@0 80 /**
Chris@0 81 * Starts logging a given logging key on the specified connection.
Chris@0 82 *
Chris@0 83 * @param string $logging_key
Chris@0 84 * The logging key to log.
Chris@0 85 * @param string $key
Chris@0 86 * The database connection key for which we want to log.
Chris@0 87 *
Chris@0 88 * @return \Drupal\Core\Database\Log
Chris@0 89 * The query log object. Note that the log object does support richer
Chris@0 90 * methods than the few exposed through the Database class, so in some
Chris@0 91 * cases it may be desirable to access it directly.
Chris@0 92 *
Chris@0 93 * @see \Drupal\Core\Database\Log
Chris@0 94 */
Chris@0 95 final public static function startLog($logging_key, $key = 'default') {
Chris@0 96 if (empty(self::$logs[$key])) {
Chris@0 97 self::$logs[$key] = new Log($key);
Chris@0 98
Chris@0 99 // Every target already active for this connection key needs to have the
Chris@0 100 // logging object associated with it.
Chris@0 101 if (!empty(self::$connections[$key])) {
Chris@0 102 foreach (self::$connections[$key] as $connection) {
Chris@0 103 $connection->setLogger(self::$logs[$key]);
Chris@0 104 }
Chris@0 105 }
Chris@0 106 }
Chris@0 107
Chris@0 108 self::$logs[$key]->start($logging_key);
Chris@0 109 return self::$logs[$key];
Chris@0 110 }
Chris@0 111
Chris@0 112 /**
Chris@0 113 * Retrieves the queries logged on for given logging key.
Chris@0 114 *
Chris@0 115 * This method also ends logging for the specified key. To get the query log
Chris@0 116 * to date without ending the logger request the logging object by starting
Chris@0 117 * it again (which does nothing to an open log key) and call methods on it as
Chris@0 118 * desired.
Chris@0 119 *
Chris@0 120 * @param string $logging_key
Chris@0 121 * The logging key to log.
Chris@0 122 * @param string $key
Chris@0 123 * The database connection key for which we want to log.
Chris@0 124 *
Chris@0 125 * @return array
Chris@0 126 * The query log for the specified logging key and connection.
Chris@0 127 *
Chris@0 128 * @see \Drupal\Core\Database\Log
Chris@0 129 */
Chris@0 130 final public static function getLog($logging_key, $key = 'default') {
Chris@0 131 if (empty(self::$logs[$key])) {
Chris@0 132 return [];
Chris@0 133 }
Chris@0 134 $queries = self::$logs[$key]->get($logging_key);
Chris@0 135 self::$logs[$key]->end($logging_key);
Chris@0 136 return $queries;
Chris@0 137 }
Chris@0 138
Chris@0 139 /**
Chris@0 140 * Gets the connection object for the specified database key and target.
Chris@0 141 *
Chris@0 142 * @param string $target
Chris@0 143 * The database target name.
Chris@0 144 * @param string $key
Chris@0 145 * The database connection key. Defaults to NULL which means the active key.
Chris@0 146 *
Chris@0 147 * @return \Drupal\Core\Database\Connection
Chris@0 148 * The corresponding connection object.
Chris@0 149 */
Chris@0 150 final public static function getConnection($target = 'default', $key = NULL) {
Chris@0 151 if (!isset($key)) {
Chris@0 152 // By default, we want the active connection, set in setActiveConnection.
Chris@0 153 $key = self::$activeKey;
Chris@0 154 }
Chris@0 155 // If the requested target does not exist, or if it is ignored, we fall back
Chris@0 156 // to the default target. The target is typically either "default" or
Chris@0 157 // "replica", indicating to use a replica SQL server if one is available. If
Chris@0 158 // it's not available, then the default/primary server is the correct server
Chris@0 159 // to use.
Chris@0 160 if (!empty(self::$ignoreTargets[$key][$target]) || !isset(self::$databaseInfo[$key][$target])) {
Chris@0 161 $target = 'default';
Chris@0 162 }
Chris@0 163
Chris@0 164 if (!isset(self::$connections[$key][$target])) {
Chris@0 165 // If necessary, a new connection is opened.
Chris@0 166 self::$connections[$key][$target] = self::openConnection($key, $target);
Chris@0 167 }
Chris@0 168 return self::$connections[$key][$target];
Chris@0 169 }
Chris@0 170
Chris@0 171 /**
Chris@0 172 * Determines if there is an active connection.
Chris@0 173 *
Chris@0 174 * Note that this method will return FALSE if no connection has been
Chris@0 175 * established yet, even if one could be.
Chris@0 176 *
Chris@0 177 * @return bool
Chris@0 178 * TRUE if there is at least one database connection established, FALSE
Chris@0 179 * otherwise.
Chris@0 180 */
Chris@0 181 final public static function isActiveConnection() {
Chris@0 182 return !empty(self::$activeKey) && !empty(self::$connections) && !empty(self::$connections[self::$activeKey]);
Chris@0 183 }
Chris@0 184
Chris@0 185 /**
Chris@0 186 * Sets the active connection to the specified key.
Chris@0 187 *
Chris@0 188 * @return string|null
Chris@0 189 * The previous database connection key.
Chris@0 190 */
Chris@0 191 final public static function setActiveConnection($key = 'default') {
Chris@0 192 if (!empty(self::$databaseInfo[$key])) {
Chris@0 193 $old_key = self::$activeKey;
Chris@0 194 self::$activeKey = $key;
Chris@0 195 return $old_key;
Chris@0 196 }
Chris@0 197 }
Chris@0 198
Chris@0 199 /**
Chris@0 200 * Process the configuration file for database information.
Chris@0 201 *
Chris@0 202 * @param array $info
Chris@0 203 * The database connection information, as defined in settings.php. The
Chris@0 204 * structure of this array depends on the database driver it is connecting
Chris@0 205 * to.
Chris@0 206 */
Chris@0 207 final public static function parseConnectionInfo(array $info) {
Chris@0 208 // If there is no "driver" property, then we assume it's an array of
Chris@0 209 // possible connections for this target. Pick one at random. That allows
Chris@0 210 // us to have, for example, multiple replica servers.
Chris@0 211 if (empty($info['driver'])) {
Chris@0 212 $info = $info[mt_rand(0, count($info) - 1)];
Chris@0 213 }
Chris@0 214 // Parse the prefix information.
Chris@0 215 if (!isset($info['prefix'])) {
Chris@0 216 // Default to an empty prefix.
Chris@0 217 $info['prefix'] = [
Chris@0 218 'default' => '',
Chris@0 219 ];
Chris@0 220 }
Chris@0 221 elseif (!is_array($info['prefix'])) {
Chris@0 222 // Transform the flat form into an array form.
Chris@0 223 $info['prefix'] = [
Chris@0 224 'default' => $info['prefix'],
Chris@0 225 ];
Chris@0 226 }
Chris@0 227 return $info;
Chris@0 228 }
Chris@0 229
Chris@0 230 /**
Chris@0 231 * Adds database connection information for a given key/target.
Chris@0 232 *
Chris@0 233 * This method allows to add new connections at runtime.
Chris@0 234 *
Chris@0 235 * Under normal circumstances the preferred way to specify database
Chris@0 236 * credentials is via settings.php. However, this method allows them to be
Chris@0 237 * added at arbitrary times, such as during unit tests, when connecting to
Chris@0 238 * admin-defined third party databases, etc.
Chris@0 239 *
Chris@0 240 * If the given key/target pair already exists, this method will be ignored.
Chris@0 241 *
Chris@0 242 * @param string $key
Chris@0 243 * The database key.
Chris@0 244 * @param string $target
Chris@0 245 * The database target name.
Chris@0 246 * @param array $info
Chris@0 247 * The database connection information, as defined in settings.php. The
Chris@0 248 * structure of this array depends on the database driver it is connecting
Chris@0 249 * to.
Chris@0 250 */
Chris@0 251 final public static function addConnectionInfo($key, $target, array $info) {
Chris@0 252 if (empty(self::$databaseInfo[$key][$target])) {
Chris@0 253 self::$databaseInfo[$key][$target] = self::parseConnectionInfo($info);
Chris@0 254 }
Chris@0 255 }
Chris@0 256
Chris@0 257 /**
Chris@0 258 * Gets information on the specified database connection.
Chris@0 259 *
Chris@0 260 * @param string $key
Chris@0 261 * (optional) The connection key for which to return information.
Chris@0 262 *
Chris@0 263 * @return array|null
Chris@0 264 */
Chris@0 265 final public static function getConnectionInfo($key = 'default') {
Chris@0 266 if (!empty(self::$databaseInfo[$key])) {
Chris@0 267 return self::$databaseInfo[$key];
Chris@0 268 }
Chris@0 269 }
Chris@0 270
Chris@0 271 /**
Chris@0 272 * Gets connection information for all available databases.
Chris@0 273 *
Chris@0 274 * @return array
Chris@0 275 */
Chris@0 276 final public static function getAllConnectionInfo() {
Chris@0 277 return self::$databaseInfo;
Chris@0 278 }
Chris@0 279
Chris@0 280 /**
Chris@0 281 * Sets connection information for multiple databases.
Chris@0 282 *
Chris@0 283 * @param array $databases
Chris@0 284 * A multi-dimensional array specifying database connection parameters, as
Chris@0 285 * defined in settings.php.
Chris@0 286 */
Chris@0 287 final public static function setMultipleConnectionInfo(array $databases) {
Chris@0 288 foreach ($databases as $key => $targets) {
Chris@0 289 foreach ($targets as $target => $info) {
Chris@0 290 self::addConnectionInfo($key, $target, $info);
Chris@0 291 }
Chris@0 292 }
Chris@0 293 }
Chris@0 294
Chris@0 295 /**
Chris@0 296 * Rename a connection and its corresponding connection information.
Chris@0 297 *
Chris@0 298 * @param string $old_key
Chris@0 299 * The old connection key.
Chris@0 300 * @param string $new_key
Chris@0 301 * The new connection key.
Chris@0 302 *
Chris@0 303 * @return bool
Chris@0 304 * TRUE in case of success, FALSE otherwise.
Chris@0 305 */
Chris@0 306 final public static function renameConnection($old_key, $new_key) {
Chris@0 307 if (!empty(self::$databaseInfo[$old_key]) && empty(self::$databaseInfo[$new_key])) {
Chris@0 308 // Migrate the database connection information.
Chris@0 309 self::$databaseInfo[$new_key] = self::$databaseInfo[$old_key];
Chris@0 310 unset(self::$databaseInfo[$old_key]);
Chris@0 311
Chris@0 312 // Migrate over the DatabaseConnection object if it exists.
Chris@0 313 if (isset(self::$connections[$old_key])) {
Chris@0 314 self::$connections[$new_key] = self::$connections[$old_key];
Chris@0 315 unset(self::$connections[$old_key]);
Chris@0 316 }
Chris@0 317
Chris@0 318 return TRUE;
Chris@0 319 }
Chris@0 320 else {
Chris@0 321 return FALSE;
Chris@0 322 }
Chris@0 323 }
Chris@0 324
Chris@0 325 /**
Chris@0 326 * Remove a connection and its corresponding connection information.
Chris@0 327 *
Chris@0 328 * @param string $key
Chris@0 329 * The connection key.
Chris@0 330 *
Chris@0 331 * @return bool
Chris@0 332 * TRUE in case of success, FALSE otherwise.
Chris@0 333 */
Chris@0 334 final public static function removeConnection($key) {
Chris@0 335 if (isset(self::$databaseInfo[$key])) {
Chris@0 336 self::closeConnection(NULL, $key);
Chris@0 337 unset(self::$databaseInfo[$key]);
Chris@0 338 return TRUE;
Chris@0 339 }
Chris@0 340 else {
Chris@0 341 return FALSE;
Chris@0 342 }
Chris@0 343 }
Chris@0 344
Chris@0 345 /**
Chris@0 346 * Opens a connection to the server specified by the given key and target.
Chris@0 347 *
Chris@0 348 * @param string $key
Chris@0 349 * The database connection key, as specified in settings.php. The default is
Chris@0 350 * "default".
Chris@0 351 * @param string $target
Chris@0 352 * The database target to open.
Chris@0 353 *
Chris@0 354 * @throws \Drupal\Core\Database\ConnectionNotDefinedException
Chris@0 355 * @throws \Drupal\Core\Database\DriverNotSpecifiedException
Chris@0 356 */
Chris@0 357 final protected static function openConnection($key, $target) {
Chris@0 358 // If the requested database does not exist then it is an unrecoverable
Chris@0 359 // error.
Chris@0 360 if (!isset(self::$databaseInfo[$key])) {
Chris@0 361 throw new ConnectionNotDefinedException('The specified database connection is not defined: ' . $key);
Chris@0 362 }
Chris@0 363
Chris@0 364 if (!$driver = self::$databaseInfo[$key][$target]['driver']) {
Chris@0 365 throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key);
Chris@0 366 }
Chris@0 367
Chris@17 368 $namespace = static::getDatabaseDriverNamespace(self::$databaseInfo[$key][$target]);
Chris@17 369 $driver_class = $namespace . '\\Connection';
Chris@0 370
Chris@0 371 $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
Chris@0 372 $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
Chris@0 373 $new_connection->setTarget($target);
Chris@0 374 $new_connection->setKey($key);
Chris@0 375
Chris@0 376 // If we have any active logging objects for this connection key, we need
Chris@0 377 // to associate them with the connection we just opened.
Chris@0 378 if (!empty(self::$logs[$key])) {
Chris@0 379 $new_connection->setLogger(self::$logs[$key]);
Chris@0 380 }
Chris@0 381
Chris@0 382 return $new_connection;
Chris@0 383 }
Chris@0 384
Chris@0 385 /**
Chris@0 386 * Closes a connection to the server specified by the given key and target.
Chris@0 387 *
Chris@0 388 * @param string $target
Chris@0 389 * The database target name. Defaults to NULL meaning that all target
Chris@0 390 * connections will be closed.
Chris@0 391 * @param string $key
Chris@0 392 * The database connection key. Defaults to NULL which means the active key.
Chris@0 393 */
Chris@0 394 public static function closeConnection($target = NULL, $key = NULL) {
Chris@0 395 // Gets the active connection by default.
Chris@0 396 if (!isset($key)) {
Chris@0 397 $key = self::$activeKey;
Chris@0 398 }
Chris@0 399 // To close a connection, it needs to be set to NULL and removed from the
Chris@0 400 // static variable. In all cases, closeConnection() might be called for a
Chris@0 401 // connection that was not opened yet, in which case the key is not defined
Chris@0 402 // yet and we just ensure that the connection key is undefined.
Chris@0 403 if (isset($target)) {
Chris@0 404 if (isset(self::$connections[$key][$target])) {
Chris@0 405 self::$connections[$key][$target]->destroy();
Chris@0 406 self::$connections[$key][$target] = NULL;
Chris@0 407 }
Chris@0 408 unset(self::$connections[$key][$target]);
Chris@0 409 }
Chris@0 410 else {
Chris@0 411 if (isset(self::$connections[$key])) {
Chris@0 412 foreach (self::$connections[$key] as $target => $connection) {
Chris@0 413 self::$connections[$key][$target]->destroy();
Chris@0 414 self::$connections[$key][$target] = NULL;
Chris@0 415 }
Chris@0 416 }
Chris@0 417 unset(self::$connections[$key]);
Chris@0 418 }
Chris@0 419 }
Chris@0 420
Chris@0 421 /**
Chris@0 422 * Instructs the system to temporarily ignore a given key/target.
Chris@0 423 *
Chris@0 424 * At times we need to temporarily disable replica queries. To do so, call this
Chris@0 425 * method with the database key and the target to disable. That database key
Chris@0 426 * will then always fall back to 'default' for that key, even if it's defined.
Chris@0 427 *
Chris@0 428 * @param string $key
Chris@0 429 * The database connection key.
Chris@0 430 * @param string $target
Chris@0 431 * The target of the specified key to ignore.
Chris@0 432 */
Chris@0 433 public static function ignoreTarget($key, $target) {
Chris@0 434 self::$ignoreTargets[$key][$target] = TRUE;
Chris@0 435 }
Chris@0 436
Chris@0 437 /**
Chris@0 438 * Converts a URL to a database connection info array.
Chris@0 439 *
Chris@0 440 * @param string $url
Chris@0 441 * The URL.
Chris@0 442 * @param string $root
Chris@0 443 * The root directory of the Drupal installation.
Chris@0 444 *
Chris@0 445 * @return array
Chris@0 446 * The database connection info.
Chris@0 447 *
Chris@0 448 * @throws \InvalidArgumentException
Chris@0 449 * Exception thrown when the provided URL does not meet the minimum
Chris@0 450 * requirements.
Chris@0 451 */
Chris@0 452 public static function convertDbUrlToConnectionInfo($url, $root) {
Chris@17 453 // Check that the URL is well formed, starting with 'scheme://', where
Chris@17 454 // 'scheme' is a database driver name.
Chris@17 455 if (preg_match('/^(.*):\/\//', $url, $matches) !== 1) {
Chris@17 456 throw new \InvalidArgumentException("Missing scheme in URL '$url'");
Chris@0 457 }
Chris@17 458 $driver = $matches[1];
Chris@0 459
Chris@17 460 // Discover if the URL has a valid driver scheme. Try with core drivers
Chris@17 461 // first.
Chris@17 462 $connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
Chris@17 463 if (!class_exists($connection_class)) {
Chris@17 464 // If the URL is not relative to a core driver, try with custom ones.
Chris@17 465 $connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection";
Chris@17 466 if (!class_exists($connection_class)) {
Chris@17 467 throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$connection_class' does not exist");
Chris@17 468 }
Chris@0 469 }
Chris@0 470
Chris@17 471 return $connection_class::createConnectionOptionsFromUrl($url, $root);
Chris@0 472 }
Chris@0 473
Chris@0 474 /**
Chris@0 475 * Gets database connection info as a URL.
Chris@0 476 *
Chris@0 477 * @param string $key
Chris@0 478 * (Optional) The database connection key.
Chris@0 479 *
Chris@0 480 * @return string
Chris@0 481 * The connection info as a URL.
Chris@17 482 *
Chris@17 483 * @throws \RuntimeException
Chris@17 484 * When the database connection is not defined.
Chris@0 485 */
Chris@0 486 public static function getConnectionInfoAsUrl($key = 'default') {
Chris@0 487 $db_info = static::getConnectionInfo($key);
Chris@17 488 if (empty($db_info) || empty($db_info['default'])) {
Chris@17 489 throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
Chris@0 490 }
Chris@17 491 $connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection';
Chris@17 492 return $connection_class::createUrlFromConnectionOptions($db_info['default']);
Chris@17 493 }
Chris@0 494
Chris@17 495 /**
Chris@17 496 * Gets the PHP namespace of a database driver from the connection info.
Chris@17 497 *
Chris@17 498 * @param array $connection_info
Chris@17 499 * The database connection information, as defined in settings.php. The
Chris@17 500 * structure of this array depends on the database driver it is connecting
Chris@17 501 * to.
Chris@17 502 *
Chris@17 503 * @return string
Chris@17 504 * The PHP namespace of the driver's database.
Chris@17 505 */
Chris@17 506 protected static function getDatabaseDriverNamespace(array $connection_info) {
Chris@17 507 if (isset($connection_info['namespace'])) {
Chris@17 508 return $connection_info['namespace'];
Chris@0 509 }
Chris@17 510 // Fallback for Drupal 7 settings.php.
Chris@17 511 return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];
Chris@0 512 }
Chris@0 513
Chris@0 514 }