3 namespace Drupal\Core\Database;
6 * Primary front-controller for the database system.
8 * This class is uninstantiatable and un-extendable. It acts to encapsulate
9 * all control and shepherding of database connections into a single location
10 * without the use of globals.
12 abstract class Database {
15 * Flag to indicate a query call should simply return NULL.
17 * This is used for queries that have no reasonable return value anyway, such
18 * as INSERT statements to a table without a serial primary key.
20 const RETURN_NULL = 0;
23 * Flag to indicate a query call should return the prepared statement.
25 const RETURN_STATEMENT = 1;
28 * Flag to indicate a query call should return the number of affected rows.
30 const RETURN_AFFECTED = 2;
33 * Flag to indicate a query call should return the "last insert id".
35 const RETURN_INSERT_ID = 3;
38 * An nested array of all active connections. It is keyed by database name
43 static protected $connections = [];
46 * A processed copy of the database connection information from settings.php.
50 static protected $databaseInfo = [];
53 * A list of key/target credentials to simply ignore.
57 static protected $ignoreTargets = [];
60 * The key of the currently active database connection.
64 static protected $activeKey = 'default';
67 * An array of active query log objects.
69 * Every connection has one and only one logger object for all targets and
73 * '$db_key' => DatabaseLog object.
78 static protected $logs = [];
81 * Starts logging a given logging key on the specified connection.
83 * @param string $logging_key
84 * The logging key to log.
86 * The database connection key for which we want to log.
88 * @return \Drupal\Core\Database\Log
89 * The query log object. Note that the log object does support richer
90 * methods than the few exposed through the Database class, so in some
91 * cases it may be desirable to access it directly.
93 * @see \Drupal\Core\Database\Log
95 final public static function startLog($logging_key, $key = 'default') {
96 if (empty(self::$logs[$key])) {
97 self::$logs[$key] = new Log($key);
99 // Every target already active for this connection key needs to have the
100 // logging object associated with it.
101 if (!empty(self::$connections[$key])) {
102 foreach (self::$connections[$key] as $connection) {
103 $connection->setLogger(self::$logs[$key]);
108 self::$logs[$key]->start($logging_key);
109 return self::$logs[$key];
113 * Retrieves the queries logged on for given logging key.
115 * This method also ends logging for the specified key. To get the query log
116 * to date without ending the logger request the logging object by starting
117 * it again (which does nothing to an open log key) and call methods on it as
120 * @param string $logging_key
121 * The logging key to log.
123 * The database connection key for which we want to log.
126 * The query log for the specified logging key and connection.
128 * @see \Drupal\Core\Database\Log
130 final public static function getLog($logging_key, $key = 'default') {
131 if (empty(self::$logs[$key])) {
134 $queries = self::$logs[$key]->get($logging_key);
135 self::$logs[$key]->end($logging_key);
140 * Gets the connection object for the specified database key and target.
142 * @param string $target
143 * The database target name.
145 * The database connection key. Defaults to NULL which means the active key.
147 * @return \Drupal\Core\Database\Connection
148 * The corresponding connection object.
150 final public static function getConnection($target = 'default', $key = NULL) {
152 // By default, we want the active connection, set in setActiveConnection.
153 $key = self::$activeKey;
155 // If the requested target does not exist, or if it is ignored, we fall back
156 // to the default target. The target is typically either "default" or
157 // "replica", indicating to use a replica SQL server if one is available. If
158 // it's not available, then the default/primary server is the correct server
160 if (!empty(self::$ignoreTargets[$key][$target]) || !isset(self::$databaseInfo[$key][$target])) {
164 if (!isset(self::$connections[$key][$target])) {
165 // If necessary, a new connection is opened.
166 self::$connections[$key][$target] = self::openConnection($key, $target);
168 return self::$connections[$key][$target];
172 * Determines if there is an active connection.
174 * Note that this method will return FALSE if no connection has been
175 * established yet, even if one could be.
178 * TRUE if there is at least one database connection established, FALSE
181 final public static function isActiveConnection() {
182 return !empty(self::$activeKey) && !empty(self::$connections) && !empty(self::$connections[self::$activeKey]);
186 * Sets the active connection to the specified key.
188 * @return string|null
189 * The previous database connection key.
191 final public static function setActiveConnection($key = 'default') {
192 if (!empty(self::$databaseInfo[$key])) {
193 $old_key = self::$activeKey;
194 self::$activeKey = $key;
200 * Process the configuration file for database information.
203 * The database connection information, as defined in settings.php. The
204 * structure of this array depends on the database driver it is connecting
207 final public static function parseConnectionInfo(array $info) {
208 // If there is no "driver" property, then we assume it's an array of
209 // possible connections for this target. Pick one at random. That allows
210 // us to have, for example, multiple replica servers.
211 if (empty($info['driver'])) {
212 $info = $info[mt_rand(0, count($info) - 1)];
214 // Parse the prefix information.
215 if (!isset($info['prefix'])) {
216 // Default to an empty prefix.
221 elseif (!is_array($info['prefix'])) {
222 // Transform the flat form into an array form.
224 'default' => $info['prefix'],
231 * Adds database connection information for a given key/target.
233 * This method allows to add new connections at runtime.
235 * Under normal circumstances the preferred way to specify database
236 * credentials is via settings.php. However, this method allows them to be
237 * added at arbitrary times, such as during unit tests, when connecting to
238 * admin-defined third party databases, etc.
240 * If the given key/target pair already exists, this method will be ignored.
244 * @param string $target
245 * The database target name.
247 * The database connection information, as defined in settings.php. The
248 * structure of this array depends on the database driver it is connecting
251 final public static function addConnectionInfo($key, $target, array $info) {
252 if (empty(self::$databaseInfo[$key][$target])) {
253 self::$databaseInfo[$key][$target] = self::parseConnectionInfo($info);
258 * Gets information on the specified database connection.
261 * (optional) The connection key for which to return information.
265 final public static function getConnectionInfo($key = 'default') {
266 if (!empty(self::$databaseInfo[$key])) {
267 return self::$databaseInfo[$key];
272 * Gets connection information for all available databases.
276 final public static function getAllConnectionInfo() {
277 return self::$databaseInfo;
281 * Sets connection information for multiple databases.
283 * @param array $databases
284 * A multi-dimensional array specifying database connection parameters, as
285 * defined in settings.php.
287 final public static function setMultipleConnectionInfo(array $databases) {
288 foreach ($databases as $key => $targets) {
289 foreach ($targets as $target => $info) {
290 self::addConnectionInfo($key, $target, $info);
296 * Rename a connection and its corresponding connection information.
298 * @param string $old_key
299 * The old connection key.
300 * @param string $new_key
301 * The new connection key.
304 * TRUE in case of success, FALSE otherwise.
306 final public static function renameConnection($old_key, $new_key) {
307 if (!empty(self::$databaseInfo[$old_key]) && empty(self::$databaseInfo[$new_key])) {
308 // Migrate the database connection information.
309 self::$databaseInfo[$new_key] = self::$databaseInfo[$old_key];
310 unset(self::$databaseInfo[$old_key]);
312 // Migrate over the DatabaseConnection object if it exists.
313 if (isset(self::$connections[$old_key])) {
314 self::$connections[$new_key] = self::$connections[$old_key];
315 unset(self::$connections[$old_key]);
326 * Remove a connection and its corresponding connection information.
329 * The connection key.
332 * TRUE in case of success, FALSE otherwise.
334 final public static function removeConnection($key) {
335 if (isset(self::$databaseInfo[$key])) {
336 self::closeConnection(NULL, $key);
337 unset(self::$databaseInfo[$key]);
346 * Opens a connection to the server specified by the given key and target.
349 * The database connection key, as specified in settings.php. The default is
351 * @param string $target
352 * The database target to open.
354 * @throws \Drupal\Core\Database\ConnectionNotDefinedException
355 * @throws \Drupal\Core\Database\DriverNotSpecifiedException
357 final protected static function openConnection($key, $target) {
358 // If the requested database does not exist then it is an unrecoverable
360 if (!isset(self::$databaseInfo[$key])) {
361 throw new ConnectionNotDefinedException('The specified database connection is not defined: ' . $key);
364 if (!$driver = self::$databaseInfo[$key][$target]['driver']) {
365 throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key);
368 $namespace = static::getDatabaseDriverNamespace(self::$databaseInfo[$key][$target]);
369 $driver_class = $namespace . '\\Connection';
371 $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
372 $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
373 $new_connection->setTarget($target);
374 $new_connection->setKey($key);
376 // If we have any active logging objects for this connection key, we need
377 // to associate them with the connection we just opened.
378 if (!empty(self::$logs[$key])) {
379 $new_connection->setLogger(self::$logs[$key]);
382 return $new_connection;
386 * Closes a connection to the server specified by the given key and target.
388 * @param string $target
389 * The database target name. Defaults to NULL meaning that all target
390 * connections will be closed.
392 * The database connection key. Defaults to NULL which means the active key.
394 public static function closeConnection($target = NULL, $key = NULL) {
395 // Gets the active connection by default.
397 $key = self::$activeKey;
399 // To close a connection, it needs to be set to NULL and removed from the
400 // static variable. In all cases, closeConnection() might be called for a
401 // connection that was not opened yet, in which case the key is not defined
402 // yet and we just ensure that the connection key is undefined.
403 if (isset($target)) {
404 if (isset(self::$connections[$key][$target])) {
405 self::$connections[$key][$target]->destroy();
406 self::$connections[$key][$target] = NULL;
408 unset(self::$connections[$key][$target]);
411 if (isset(self::$connections[$key])) {
412 foreach (self::$connections[$key] as $target => $connection) {
413 self::$connections[$key][$target]->destroy();
414 self::$connections[$key][$target] = NULL;
417 unset(self::$connections[$key]);
422 * Instructs the system to temporarily ignore a given key/target.
424 * At times we need to temporarily disable replica queries. To do so, call this
425 * method with the database key and the target to disable. That database key
426 * will then always fall back to 'default' for that key, even if it's defined.
429 * The database connection key.
430 * @param string $target
431 * The target of the specified key to ignore.
433 public static function ignoreTarget($key, $target) {
434 self::$ignoreTargets[$key][$target] = TRUE;
438 * Converts a URL to a database connection info array.
442 * @param string $root
443 * The root directory of the Drupal installation.
446 * The database connection info.
448 * @throws \InvalidArgumentException
449 * Exception thrown when the provided URL does not meet the minimum
452 public static function convertDbUrlToConnectionInfo($url, $root) {
453 // Check that the URL is well formed, starting with 'scheme://', where
454 // 'scheme' is a database driver name.
455 if (preg_match('/^(.*):\/\//', $url, $matches) !== 1) {
456 throw new \InvalidArgumentException("Missing scheme in URL '$url'");
458 $driver = $matches[1];
460 // Discover if the URL has a valid driver scheme. Try with core drivers
462 $connection_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
463 if (!class_exists($connection_class)) {
464 // If the URL is not relative to a core driver, try with custom ones.
465 $connection_class = "Drupal\\Driver\\Database\\{$driver}\\Connection";
466 if (!class_exists($connection_class)) {
467 throw new \InvalidArgumentException("Can not convert '$url' to a database connection, class '$connection_class' does not exist");
471 return $connection_class::createConnectionOptionsFromUrl($url, $root);
475 * Gets database connection info as a URL.
478 * (Optional) The database connection key.
481 * The connection info as a URL.
483 * @throws \RuntimeException
484 * When the database connection is not defined.
486 public static function getConnectionInfoAsUrl($key = 'default') {
487 $db_info = static::getConnectionInfo($key);
488 if (empty($db_info) || empty($db_info['default'])) {
489 throw new \RuntimeException("Database connection $key not defined or missing the 'default' settings");
491 $connection_class = static::getDatabaseDriverNamespace($db_info['default']) . '\\Connection';
492 return $connection_class::createUrlFromConnectionOptions($db_info['default']);
496 * Gets the PHP namespace of a database driver from the connection info.
498 * @param array $connection_info
499 * The database connection information, as defined in settings.php. The
500 * structure of this array depends on the database driver it is connecting
504 * The PHP namespace of the driver's database.
506 protected static function getDatabaseDriverNamespace(array $connection_info) {
507 if (isset($connection_info['namespace'])) {
508 return $connection_info['namespace'];
510 // Fallback for Drupal 7 settings.php.
511 return 'Drupal\\Core\\Database\\Driver\\' . $connection_info['driver'];