Commit 8df23c8a authored by Dries's avatar Dries

- Patch #1605324 by sun, Jose Reyero, alexpott, chx, Rob Loach, beejeebus:...

- Patch #1605324 by sun, Jose Reyero, alexpott, chx, Rob Loach, beejeebus: configuration system cleanup and rearchitecture.
parent 48a0b693
...@@ -288,8 +288,6 @@ ...@@ -288,8 +288,6 @@
*/ */
const DRUPAL_PHP_FUNCTION_PATTERN = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*'; const DRUPAL_PHP_FUNCTION_PATTERN = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
require_once DRUPAL_ROOT . '/core/includes/config.inc';
/** /**
* Starts the timer with the specified name. * Starts the timer with the specified name.
* *
...@@ -479,6 +477,25 @@ function find_conf_path($http_host, $script_name, $require_settings = TRUE) { ...@@ -479,6 +477,25 @@ function find_conf_path($http_host, $script_name, $require_settings = TRUE) {
return $conf; return $conf;
} }
/**
* Returns the path of the configuration directory.
*
* @return string
* The configuration directory path.
*/
function config_get_config_directory() {
global $config_directory_name;
if ($test_prefix = drupal_valid_test_ua()) {
// @see Drupal\simpletest\WebTestBase::setUp()
$path = conf_path() . '/files/simpletest/' . substr($test_prefix, 10) . '/config';
}
else {
$path = conf_path() . '/files/' . $config_directory_name;
}
return $path;
}
/** /**
* Sets appropriate server variables needed for command line scripts to work. * Sets appropriate server variables needed for command line scripts to work.
* *
...@@ -2265,6 +2282,9 @@ function _drupal_bootstrap_configuration() { ...@@ -2265,6 +2282,9 @@ function _drupal_bootstrap_configuration() {
// Activate the class loader. // Activate the class loader.
drupal_classloader(); drupal_classloader();
// Load the procedural configuration system helper functions.
require_once DRUPAL_ROOT . '/core/includes/config.inc';
} }
/** /**
......
...@@ -8,25 +8,6 @@ ...@@ -8,25 +8,6 @@
* This is the API for configuration storage. * This is the API for configuration storage.
*/ */
/**
* Gets the randomly generated config directory name.
*
* @return
* The directory name.
*/
function config_get_config_directory() {
global $config_directory_name;
if ($test_prefix = drupal_valid_test_ua()) {
// @see Drupal\simpletest\WebTestBase::setUp()
$path = conf_path() . '/files/simpletest/' . substr($test_prefix, 10) . '/config';
}
else {
$path = conf_path() . '/files/' . $config_directory_name;
}
return $path;
}
/** /**
* Installs the default configuration of a given module. * Installs the default configuration of a given module.
* *
...@@ -38,28 +19,23 @@ function config_get_config_directory() { ...@@ -38,28 +19,23 @@ function config_get_config_directory() {
*/ */
function config_install_default_config($module) { function config_install_default_config($module) {
$module_config_dir = drupal_get_path('module', $module) . '/config'; $module_config_dir = drupal_get_path('module', $module) . '/config';
$drupal_config_dir = config_get_config_directory(); if (is_dir($module_config_dir)) {
if (is_dir(drupal_get_path('module', $module) . '/config')) { $database_storage = new DatabaseStorage();
$files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension()); $module_file_storage = new FileStorage(array('directory' => $module_config_dir));
foreach ($files as $key => $file) {
// Load config data into the active store and write it out to the
// file system in the drupal config directory. Note the config name
// needs to be the same as the file name WITHOUT the extension.
$config_name = basename($file, '.' . FileStorage::getFileExtension());
$database_storage = new DatabaseStorage($config_name); foreach ($module_file_storage->listAll() as $config_name) {
$file_storage = new FileStorage($config_name); $data = $module_file_storage->read($config_name);
$file_storage->setPath($module_config_dir); $database_storage->write($config_name, $data);
$database_storage->write($file_storage->read());
} }
} }
} }
/** /**
* @todo http://drupal.org/node/1552396 renames this into config_load_all(). * @todo Modules need a way to access the active store, whatever it is.
*/ */
function config_get_storage_names_with_prefix($prefix = '') { function config_get_storage_names_with_prefix($prefix = '') {
return DatabaseStorage::getNamesWithPrefix($prefix); $storage = new DatabaseStorage();
return $storage->listAll($prefix);
} }
/** /**
...@@ -73,16 +49,11 @@ function config_get_storage_names_with_prefix($prefix = '') { ...@@ -73,16 +49,11 @@ function config_get_storage_names_with_prefix($prefix = '') {
* The name of the configuration object to retrieve. The name corresponds to * The name of the configuration object to retrieve. The name corresponds to
* a configuration file. For @code config(book.admin) @endcode, the config * a configuration file. For @code config(book.admin) @endcode, the config
* object returned will contain the contents of book.admin configuration file. * object returned will contain the contents of book.admin configuration file.
* @param $class
* The class name of the config object to be returned. Defaults to
* DrupalConfig.
* *
* @return * @return Drupal\Core\Config\Config
* An instance of the class specified in the $class parameter. * A configuration object.
*
* @todo Replace this with an appropriate factory / ability to inject in
* alternate storage engines..
*/ */
function config($name, $class = 'Drupal\Core\Config\DrupalConfig') { function config($name) {
return new $class(new DatabaseStorage($name)); return drupal_container()->get('config.factory')->get($name)->load();
} }
<?php <?php
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\DatabaseStorage;
/** /**
* Indicates that a module has not been installed yet. * Indicates that a module has not been installed yet.
...@@ -376,24 +376,17 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents ...@@ -376,24 +376,17 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents
$module_list = array_keys($module_list); $module_list = array_keys($module_list);
} }
$storage = new DatabaseStorage();
foreach ($module_list as $module) { foreach ($module_list as $module) {
// Uninstall the module. // Uninstall the module.
module_load_install($module); module_load_install($module);
module_invoke($module, 'uninstall'); module_invoke($module, 'uninstall');
drupal_uninstall_schema($module); drupal_uninstall_schema($module);
// Remove any stray configuration settings. // Remove all configuration belonging to the module.
// Get the names of default configurations provided by this module $config_names = $storage->listAll($module . '.');
// by scanning its config directory. foreach ($config_names as $config_name) {
$module_config_dir = drupal_get_path('module', $module) . '/config'; config($config_name)->delete();
if (is_dir($module_config_dir)) {
$files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension());
foreach ($files as $file) {
$config_name = basename($file, '.' . FileStorage::getFileExtension());
$file_storage = new FileStorage($config_name);
// Delete the configuration from storage.
$file_storage->delete();
}
} }
watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
......
...@@ -487,7 +487,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) { ...@@ -487,7 +487,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
$versions = drupal_get_schema_versions($module); $versions = drupal_get_schema_versions($module);
$version = $versions ? max($versions) : SCHEMA_INSTALLED; $version = $versions ? max($versions) : SCHEMA_INSTALLED;
// Copy any default configuration data to the system config directory/ // Install default configuration of the module.
config_install_default_config($module); config_install_default_config($module);
// If the module has no current updates, but has some that were // If the module has no current updates, but has some that were
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
use Drupal\Component\Graph\Graph; use Drupal\Component\Graph\Graph;
use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\ConfigException;
/** /**
* Minimum schema version of Drupal 7 required for upgrade to Drupal 8. * Minimum schema version of Drupal 7 required for upgrade to Drupal 8.
...@@ -942,11 +943,11 @@ function update_variables_to_config($config_name, array $variable_map) { ...@@ -942,11 +943,11 @@ function update_variables_to_config($config_name, array $variable_map) {
$module = strtok($config_name, '.'); $module = strtok($config_name, '.');
// Load and set default configuration values. // Load and set default configuration values.
// Throws a FileStorageReadException if there is no default configuration $file = new FileStorage(array('directory' => drupal_get_path('module', $module) . '/config'));
// file, which is required to exist. if (!$file->exists($config_name)) {
$file = new FileStorage($config_name); throw new ConfigException("Default configuration file $config_name for $module extension not found but is required to exist.");
$file->setPath(drupal_get_path('module', $module) . '/config'); }
$default_data = $file->read(); $default_data = $file->read($config_name);
// Merge any possibly existing original data into default values. // Merge any possibly existing original data into default values.
// Only relevant when being called repetitively on the same config object. // Only relevant when being called repetitively on the same config object.
......
<?php <?php
/**
* @file
* Definition of Drupal\Core\Config\ConfigException.
*/
namespace Drupal\Core\Config; namespace Drupal\Core\Config;
use RuntimeException;
/** /**
* @todo * A base exception thrown in any configuration system operations.
*/ */
class ConfigException extends \Exception {} class ConfigException extends RuntimeException {}
<?php <?php
/**
* @file
* Definition of Drupal\Core\Config\DatabaseStorage.
*/
namespace Drupal\Core\Config; namespace Drupal\Core\Config;
use Drupal\Core\Config\StorageBase; use Drupal\Core\Database\Database;
use Exception; use Exception;
/** /**
* Represents an SQL-based configuration storage object. * Defines the Database storage controller.
*/ */
class DatabaseStorage extends StorageBase { class DatabaseStorage implements StorageInterface {
/**
* Database connection options for this storage controller.
*
* - connection: The connection key to use.
* - target: The target on the connection to use.
*
* @var array
*/
protected $options;
/**
* Implements Drupal\Core\Config\StorageInterface::__construct().
*/
public function __construct(array $options = array()) {
$options += array(
'connection' => 'default',
'target' => 'default',
);
$this->options = $options;
}
/**
* Returns the database connection to use.
*/
protected function getConnection() {
return Database::getConnection($this->options['target'], $this->options['connection']);
}
/** /**
* Implements StorageInterface::read(). * Implements Drupal\Core\Config\StorageInterface::read().
*
* @throws PDOException
* @throws Drupal\Core\Database\DatabaseExceptionWrapper
* Only thrown in case $this->options['throw_exception'] is TRUE.
*/ */
public function read() { public function read($name) {
$data = array();
// There are situations, like in the installer, where we may attempt a // There are situations, like in the installer, where we may attempt a
// read without actually having the database available. In this case, // read without actually having the database available. In this case,
// catch the exception and just return an empty array so the caller can // catch the exception and just return an empty array so the caller can
// handle it if need be. // handle it if need be.
$data = array(); // @todo Remove this and use appropriate StorageDispatcher configuration in
// the installer instead.
try { try {
$raw = db_query('SELECT data FROM {config} WHERE name = :name', array(':name' => $this->name))->fetchField(); $raw = $this->getConnection()->query('SELECT data FROM {config} WHERE name = :name', array(':name' => $name), $this->options)->fetchField();
if ($raw !== FALSE) { if ($raw !== FALSE) {
$data = $this->decode($raw); $data = $this->decode($raw);
} }
...@@ -31,43 +70,63 @@ public function read() { ...@@ -31,43 +70,63 @@ public function read() {
} }
/** /**
* Implements StorageInterface::writeToActive(). * Implements Drupal\Core\Config\StorageInterface::write().
*
* @throws PDOException
*
* @todo Ignore slave targets for data manipulation operations.
*/ */
public function writeToActive($data) { public function write($name, array $data) {
$data = $this->encode($data); $data = $this->encode($data);
return db_merge('config') $options = array('return' => Database::RETURN_AFFECTED) + $this->options;
->key(array('name' => $this->name)) return (bool) $this->getConnection()->merge('config', $options)
->key(array('name' => $name))
->fields(array('data' => $data)) ->fields(array('data' => $data))
->execute(); ->execute();
} }
/** /**
* @todo * Implements Drupal\Core\Config\StorageInterface::delete().
*
* @throws PDOException
*
* @todo Ignore slave targets for data manipulation operations.
*/ */
public function deleteFromActive() { public function delete($name) {
db_delete('config') $options = array('return' => Database::RETURN_AFFECTED) + $this->options;
->condition('name', $this->name) return (bool) $this->getConnection()->delete('config', $options)
->condition('name', $name)
->execute(); ->execute();
} }
/** /**
* Implements StorageInterface::encode(). * Implements Drupal\Core\Config\StorageInterface::encode().
*/ */
public static function encode($data) { public static function encode($data) {
return serialize($data); return serialize($data);
} }
/** /**
* Implements StorageInterface::decode(). * Implements Drupal\Core\Config\StorageInterface::decode().
*
* @throws ErrorException
* unserialize() triggers E_NOTICE if the string cannot be unserialized.
*/ */
public static function decode($raw) { public static function decode($raw) {
return unserialize($raw); $data = @unserialize($raw);
return $data !== FALSE ? $data : array();
} }
/** /**
* Implements StorageInterface::getNamesWithPrefix(). * Implements Drupal\Core\Config\StorageInterface::listAll().
*
* @throws PDOException
* @throws Drupal\Core\Database\DatabaseExceptionWrapper
* Only thrown in case $this->options['throw_exception'] is TRUE.
*/ */
static public function getNamesWithPrefix($prefix = '') { public function listAll($prefix = '') {
return db_query('SELECT name FROM {config} WHERE name LIKE :name', array(':name' => db_like($prefix) . '%'))->fetchCol(); return $this->getConnection()->query('SELECT name FROM {config} WHERE name LIKE :name', array(
':name' => db_like($prefix) . '%',
), $this->options)->fetchCol();
} }
} }
<?php
namespace Drupal\Core\Config;
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\ConfigException;
/**
* Represents the default configuration storage object.
*/
class DrupalConfig {
/**
* The storage engine to save this config object to.
*
* @var StorageInterface
*/
protected $storage;
/**
* The data of the configuration object.
*
* @var array
*/
protected $data = array();
/**
* Constructs a DrupalConfig object.
*
* @param StorageInterface $storage
* The storage engine where this config object should be saved.
*
* @todo $this should really know about $name and make it publicly accessible.
*/
public function __construct(StorageInterface $storage) {
$this->storage = $storage;
$this->read();
}
/**
* Reads config data from the active store into our object.
*/
public function read() {
$data = $this->storage->read();
$this->setData($data !== FALSE ? $data : array());
return $this;
}
/**
* Checks whether a particular value is overridden.
*
* @param $key
* @todo
*
* @return
* @todo
*/
public function isOverridden($key) {
return isset($this->_overrides[$key]);
}
/**
* Gets data from this config object.
*
* @param $key
* A string that maps to a key within the configuration data.
* For instance in the following configuation array:
* @code
* array(
* 'foo' => array(
* 'bar' => 'baz',
* ),
* );
* @endcode
* A key of 'foo.bar' would return the string 'baz'. However, a key of 'foo'
* would return array('bar' => 'baz').
* If no key is specified, then the entire data array is returned.
*
* The configuration system does not retain data types. Every saved value is
* casted to a string. In most cases this is not an issue; however, it can
* cause issues with Booleans, which are casted to "1" (TRUE) or "0" (FALSE).
* In particular, code relying on === or !== will no longer function properly.
*
* @see http://php.net/manual/language.operators.comparison.php.
*
* @return
* The data that was requested.
*/
public function get($key = '') {
global $conf;
$name = $this->storage->getName();
if (isset($conf[$name])) {
$merged_data = drupal_array_merge_deep($this->data, $conf[$name]);
}
else {
$merged_data = $this->data;
}
if (empty($key)) {
return $merged_data;
}
else {
$parts = explode('.', $key);
if (count($parts) == 1) {
return isset($merged_data[$key]) ? $merged_data[$key] : NULL;
}
else {
$key_exists = NULL;
$value = drupal_array_get_nested_value($merged_data, $parts, $key_exists);
return $key_exists ? $value : NULL;
}
}
}
/**
* Replaces the data of this configuration object.
*
* @param array $data
* The new configuration data.
*/
public function setData(array $data) {
$this->data = $data;
return $this;
}
/**
* Sets value in this config object.
*
* @param $key
* @todo
* @param $value
* @todo
*/
public function set($key, $value) {
// Type-cast value into a string.
$value = $this->castValue($value);
// The dot/period is a reserved character; it may appear between keys, but
// not within keys.
$parts = explode('.', $key);
if (count($parts) == 1) {
$this->data[$key] = $value;
}
else {