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