DatabaseLog object. * ); * * @var array */ static protected $logs = []; /** * Starts logging a given logging key on the specified connection. * * @param string $logging_key * The logging key to log. * @param string $key * The database connection key for which we want to log. * * @return \Drupal\Core\Database\Log * The query log object. Note that the log object does support richer * methods than the few exposed through the Database class, so in some * cases it may be desirable to access it directly. * * @see \Drupal\Core\Database\Log */ final public static function startLog($logging_key, $key = 'default') { if (empty(self::$logs[$key])) { self::$logs[$key] = new Log($key); // Every target already active for this connection key needs to have the // logging object associated with it. if (!empty(self::$connections[$key])) { foreach (self::$connections[$key] as $connection) { $connection->setLogger(self::$logs[$key]); } } } self::$logs[$key]->start($logging_key); return self::$logs[$key]; } /** * Retrieves the queries logged on for given logging key. * * This method also ends logging for the specified key. To get the query log * to date without ending the logger request the logging object by starting * it again (which does nothing to an open log key) and call methods on it as * desired. * * @param string $logging_key * The logging key to log. * @param string $key * The database connection key for which we want to log. * * @return array * The query log for the specified logging key and connection. * * @see \Drupal\Core\Database\Log */ final public static function getLog($logging_key, $key = 'default') { if (empty(self::$logs[$key])) { return []; } $queries = self::$logs[$key]->get($logging_key); self::$logs[$key]->end($logging_key); return $queries; } /** * Gets the connection object for the specified database key and target. * * @param string $target * The database target name. * @param string $key * The database connection key. Defaults to NULL which means the active key. * * @return \Drupal\Core\Database\Connection * The corresponding connection object. */ final public static function getConnection($target = 'default', $key = NULL) { if (!isset($key)) { // By default, we want the active connection, set in setActiveConnection. $key = self::$activeKey; } // If the requested target does not exist, or if it is ignored, we fall back // to the default target. The target is typically either "default" or // "replica", indicating to use a replica SQL server if one is available. If // it's not available, then the default/primary server is the correct server // to use. if (!empty(self::$ignoreTargets[$key][$target]) || !isset(self::$databaseInfo[$key][$target])) { $target = 'default'; } if (!isset(self::$connections[$key][$target])) { // If necessary, a new connection is opened. self::$connections[$key][$target] = self::openConnection($key, $target); } return self::$connections[$key][$target]; } /** * Determines if there is an active connection. * * Note that this method will return FALSE if no connection has been * established yet, even if one could be. * * @return bool * TRUE if there is at least one database connection established, FALSE * otherwise. */ final public static function isActiveConnection() { return !empty(self::$activeKey) && !empty(self::$connections) && !empty(self::$connections[self::$activeKey]); } /** * Sets the active connection to the specified key. * * @return string|null * The previous database connection key. */ final public static function setActiveConnection($key = 'default') { if (!empty(self::$databaseInfo[$key])) { $old_key = self::$activeKey; self::$activeKey = $key; return $old_key; } } /** * Process the configuration file for database information. * * @param array $info * The database connection information, as defined in settings.php. The * structure of this array depends on the database driver it is connecting * to. */ final public static function parseConnectionInfo(array $info) { // If there is no "driver" property, then we assume it's an array of // possible connections for this target. Pick one at random. That allows // us to have, for example, multiple replica servers. if (empty($info['driver'])) { $info = $info[mt_rand(0, count($info) - 1)]; } // Parse the prefix information. if (!isset($info['prefix'])) { // Default to an empty prefix. $info['prefix'] = [ 'default' => '', ]; } elseif (!is_array($info['prefix'])) { // Transform the flat form into an array form. $info['prefix'] = [ 'default' => $info['prefix'], ]; } return $info; } /** * Adds database connection information for a given key/target. * * This method allows to add new connections at runtime. * * Under normal circumstances the preferred way to specify database * credentials is via settings.php. However, this method allows them to be * added at arbitrary times, such as during unit tests, when connecting to * admin-defined third party databases, etc. * * If the given key/target pair already exists, this method will be ignored. * * @param string $key * The database key. * @param string $target * The database target name. * @param array $info * The database connection information, as defined in settings.php. The * structure of this array depends on the database driver it is connecting * to. */ final public static function addConnectionInfo($key, $target, array $info) { if (empty(self::$databaseInfo[$key][$target])) { self::$databaseInfo[$key][$target] = self::parseConnectionInfo($info); } } /** * Gets information on the specified database connection. * * @param string $key * (optional) The connection key for which to return information. * * @return array|null */ final public static function getConnectionInfo($key = 'default') { if (!empty(self::$databaseInfo[$key])) { return self::$databaseInfo[$key]; } } /** * Gets connection information for all available databases. * * @return array */ final public static function getAllConnectionInfo() { return self::$databaseInfo; } /** * Sets connection information for multiple databases. * * @param array $databases * A multi-dimensional array specifying database connection parameters, as * defined in settings.php. */ final public static function setMultipleConnectionInfo(array $databases) { foreach ($databases as $key => $targets) { foreach ($targets as $target => $info) { self::addConnectionInfo($key, $target, $info); } } } /** * Rename a connection and its corresponding connection information. * * @param string $old_key * The old connection key. * @param string $new_key * The new connection key. * * @return bool * TRUE in case of success, FALSE otherwise. */ final public static function renameConnection($old_key, $new_key) { if (!empty(self::$databaseInfo[$old_key]) && empty(self::$databaseInfo[$new_key])) { // Migrate the database connection information. self::$databaseInfo[$new_key] = self::$databaseInfo[$old_key]; unset(self::$databaseInfo[$old_key]); // Migrate over the DatabaseConnection object if it exists. if (isset(self::$connections[$old_key])) { self::$connections[$new_key] = self::$connections[$old_key]; unset(self::$connections[$old_key]); } return TRUE; } else { return FALSE; } } /** * Remove a connection and its corresponding connection information. * * @param string $key * The connection key. * * @return bool * TRUE in case of success, FALSE otherwise. */ final public static function removeConnection($key) { if (isset(self::$databaseInfo[$key])) { self::closeConnection(NULL, $key); unset(self::$databaseInfo[$key]); return TRUE; } else { return FALSE; } } /** * Opens a connection to the server specified by the given key and target. * * @param string $key * The database connection key, as specified in settings.php. The default is * "default". * @param string $target * The database target to open. * * @throws \Drupal\Core\Database\ConnectionNotDefinedException * @throws \Drupal\Core\Database\DriverNotSpecifiedException */ final protected static function openConnection($key, $target) { // If the requested database does not exist then it is an unrecoverable // error. if (!isset(self::$databaseInfo[$key])) { throw new ConnectionNotDefinedException('The specified database connection is not defined: ' . $key); } if (!$driver = self::$databaseInfo[$key][$target]['driver']) { throw new DriverNotSpecifiedException('Driver not specified for this database connection: ' . $key); } if (!empty(self::$databaseInfo[$key][$target]['namespace'])) { $driver_class = self::$databaseInfo[$key][$target]['namespace'] . '\\Connection'; } else { // Fallback for Drupal 7 settings.php. $driver_class = "Drupal\\Core\\Database\\Driver\\{$driver}\\Connection"; } $pdo_connection = $driver_class::open(self::$databaseInfo[$key][$target]); $new_connection = new $driver_class($pdo_connection, self::$databaseInfo[$key][$target]); $new_connection->setTarget($target); $new_connection->setKey($key); // If we have any active logging objects for this connection key, we need // to associate them with the connection we just opened. if (!empty(self::$logs[$key])) { $new_connection->setLogger(self::$logs[$key]); } return $new_connection; } /** * Closes a connection to the server specified by the given key and target. * * @param string $target * The database target name. Defaults to NULL meaning that all target * connections will be closed. * @param string $key * The database connection key. Defaults to NULL which means the active key. */ public static function closeConnection($target = NULL, $key = NULL) { // Gets the active connection by default. if (!isset($key)) { $key = self::$activeKey; } // To close a connection, it needs to be set to NULL and removed from the // static variable. In all cases, closeConnection() might be called for a // connection that was not opened yet, in which case the key is not defined // yet and we just ensure that the connection key is undefined. if (isset($target)) { if (isset(self::$connections[$key][$target])) { self::$connections[$key][$target]->destroy(); self::$connections[$key][$target] = NULL; } unset(self::$connections[$key][$target]); } else { if (isset(self::$connections[$key])) { foreach (self::$connections[$key] as $target => $connection) { self::$connections[$key][$target]->destroy(); self::$connections[$key][$target] = NULL; } } unset(self::$connections[$key]); } } /** * Instructs the system to temporarily ignore a given key/target. * * At times we need to temporarily disable replica queries. To do so, call this * method with the database key and the target to disable. That database key * will then always fall back to 'default' for that key, even if it's defined. * * @param string $key * The database connection key. * @param string $target * The target of the specified key to ignore. */ public static function ignoreTarget($key, $target) { self::$ignoreTargets[$key][$target] = TRUE; } /** * Converts a URL to a database connection info array. * * @param string $url * The URL. * @param string $root * The root directory of the Drupal installation. * * @return array * The database connection info. * * @throws \InvalidArgumentException * Exception thrown when the provided URL does not meet the minimum * requirements. */ public static function convertDbUrlToConnectionInfo($url, $root) { $info = parse_url($url); if (!isset($info['scheme'], $info['host'], $info['path'])) { throw new \InvalidArgumentException('Minimum requirement: driver://host/database'); } $info += [ 'user' => '', 'pass' => '', 'fragment' => '', ]; // A SQLite database path with two leading slashes indicates a system path. // Otherwise the path is relative to the Drupal root. if ($info['path'][0] === '/') { $info['path'] = substr($info['path'], 1); } if ($info['scheme'] === 'sqlite' && $info['path'][0] !== '/') { $info['path'] = $root . '/' . $info['path']; } $database = [ 'driver' => $info['scheme'], 'username' => $info['user'], 'password' => $info['pass'], 'host' => $info['host'], 'database' => $info['path'], ]; if (isset($info['port'])) { $database['port'] = $info['port']; } return $database; } /** * Gets database connection info as a URL. * * @param string $key * (Optional) The database connection key. * * @return string * The connection info as a URL. */ public static function getConnectionInfoAsUrl($key = 'default') { $db_info = static::getConnectionInfo($key); if ($db_info['default']['driver'] == 'sqlite') { $db_url = 'sqlite://localhost/' . $db_info['default']['database']; } else { $user = ''; if ($db_info['default']['username']) { $user = $db_info['default']['username']; if ($db_info['default']['password']) { $user .= ':' . $db_info['default']['password']; } $user .= '@'; } $db_url = $db_info['default']['driver'] . '://' . $user . $db_info['default']['host']; if (isset($db_info['default']['port'])) { $db_url .= ':' . $db_info['default']['port']; } $db_url .= '/' . $db_info['default']['database']; } if ($db_info['default']['prefix']['default']) { $db_url .= '#' . $db_info['default']['prefix']['default']; } return $db_url; } }