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 if (!empty(self::$databaseInfo[$key][$target]['namespace'])) {
369 $driver_class = self::$databaseInfo[$key][$target]['namespace'] . '\\Connection';
372 // Fallback for Drupal 7 settings.php.
373 $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection";
376 $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]);
377 $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]);
378 $new_connection->setTarget($target);
379 $new_connection->setKey($key);
381 // If we have any active logging objects for this connection key, we need
382 // to associate them with the connection we just opened.
383 if (!empty(self::$logs[$key])) {
384 $new_connection->setLogger(self::$logs[$key]);
387 return $new_connection;
391 * Closes a connection to the server specified by the given key and target.
393 * @param string $target
394 * The database target name. Defaults to NULL meaning that all target
395 * connections will be closed.
397 * The database connection key. Defaults to NULL which means the active key.
399 public static function closeConnection($target = NULL, $key = NULL) {
400 // Gets the active connection by default.
402 $key = self::$activeKey;
404 // To close a connection, it needs to be set to NULL and removed from the
405 // static variable. In all cases, closeConnection() might be called for a
406 // connection that was not opened yet, in which case the key is not defined
407 // yet and we just ensure that the connection key is undefined.
408 if (isset($target)) {
409 if (isset(self::$connections[$key][$target])) {
410 self::$connections[$key][$target]->destroy();
411 self::$connections[$key][$target] = NULL;
413 unset(self::$connections[$key][$target]);
416 if (isset(self::$connections[$key])) {
417 foreach (self::$connections[$key] as $target => $connection) {
418 self::$connections[$key][$target]->destroy();
419 self::$connections[$key][$target] = NULL;
422 unset(self::$connections[$key]);
427 * Instructs the system to temporarily ignore a given key/target.
429 * At times we need to temporarily disable replica queries. To do so, call this
430 * method with the database key and the target to disable. That database key
431 * will then always fall back to 'default' for that key, even if it's defined.
434 * The database connection key.
435 * @param string $target
436 * The target of the specified key to ignore.
438 public static function ignoreTarget($key, $target) {
439 self::$ignoreTargets[$key][$target] = TRUE;
443 * Converts a URL to a database connection info array.
447 * @param string $root
448 * The root directory of the Drupal installation.
451 * The database connection info.
453 * @throws \InvalidArgumentException
454 * Exception thrown when the provided URL does not meet the minimum
457 public static function convertDbUrlToConnectionInfo($url, $root) {
458 $info = parse_url($url);
459 if (!isset($info['scheme'], $info['host'], $info['path'])) {
460 throw new \InvalidArgumentException('Minimum requirement: driver://host/database');
468 // A SQLite database path with two leading slashes indicates a system path.
469 // Otherwise the path is relative to the Drupal root.
470 if ($info['path'][0] === '/') {
471 $info['path'] = substr($info['path'], 1);
473 if ($info['scheme'] === 'sqlite' && $info['path'][0] !== '/') {
474 $info['path'] = $root . '/' . $info['path'];
478 'driver' => $info['scheme'],
479 'username' => $info['user'],
480 'password' => $info['pass'],
481 'host' => $info['host'],
482 'database' => $info['path'],
484 if (isset($info['port'])) {
485 $database['port'] = $info['port'];
491 * Gets database connection info as a URL.
494 * (Optional) The database connection key.
497 * The connection info as a URL.
499 public static function getConnectionInfoAsUrl($key = 'default') {
500 $db_info = static::getConnectionInfo($key);
501 if ($db_info['default']['driver'] == 'sqlite') {
502 $db_url = 'sqlite://localhost/' . $db_info['default']['database'];
506 if ($db_info['default']['username']) {
507 $user = $db_info['default']['username'];
508 if ($db_info['default']['password']) {
509 $user .= ':' . $db_info['default']['password'];
514 $db_url = $db_info['default']['driver'] . '://' . $user . $db_info['default']['host'];
515 if (isset($db_info['default']['port'])) {
516 $db_url .= ':' . $db_info['default']['port'];
518 $db_url .= '/' . $db_info['default']['database'];
520 if ($db_info['default']['prefix']['default']) {
521 $db_url .= '#' . $db_info['default']['prefix']['default'];