Commit 28df3aa6 authored by webchick's avatar webchick

Issue #1605324 by sun, Jose Reyero, alexpott, chx, Rob Loach | beejeebus:...

Issue #1605324 by sun, Jose Reyero, alexpott, chx, Rob Loach | beejeebus: Configuration system cleanup and rearchitecture.
parent 45013501
......@@ -288,8 +288,6 @@
*/
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.
*
......@@ -479,6 +477,25 @@ function find_conf_path($http_host, $script_name, $require_settings = TRUE) {
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.
*
......@@ -2265,6 +2282,9 @@ function _drupal_bootstrap_configuration() {
// Activate the class loader.
drupal_classloader();
// Load the procedural configuration system helper functions.
require_once DRUPAL_ROOT . '/core/includes/config.inc';
}
/**
......
......@@ -8,25 +8,6 @@
* 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.
*
......@@ -38,28 +19,23 @@ function config_get_config_directory() {
*/
function config_install_default_config($module) {
$module_config_dir = drupal_get_path('module', $module) . '/config';
$drupal_config_dir = config_get_config_directory();
if (is_dir(drupal_get_path('module', $module) . '/config')) {
$files = glob($module_config_dir . '/*.' . FileStorage::getFileExtension());
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());
if (is_dir($module_config_dir)) {
$database_storage = new DatabaseStorage();
$module_file_storage = new FileStorage(array('directory' => $module_config_dir));
$database_storage = new DatabaseStorage($config_name);
$file_storage = new FileStorage($config_name);
$file_storage->setPath($module_config_dir);
$database_storage->write($file_storage->read());
foreach ($module_file_storage->listAll() as $config_name) {
$data = $module_file_storage->read($config_name);
$database_storage->write($config_name, $data);
}
}
}
/**
* @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 = '') {
return DatabaseStorage::getNamesWithPrefix($prefix);
$storage = new DatabaseStorage();
return $storage->listAll($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
* a configuration file. For @code config(book.admin) @endcode, the config
* 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
* An instance of the class specified in the $class parameter.
*
* @todo Replace this with an appropriate factory / ability to inject in
* alternate storage engines..
* @return Drupal\Core\Config\Config
* A configuration object.
*/
function config($name, $class = 'Drupal\Core\Config\DrupalConfig') {
return new $class(new DatabaseStorage($name));
function config($name) {
return drupal_container()->get('config.factory')->get($name)->load();
}
<?php
use Drupal\Core\Database\Database;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\DatabaseStorage;
/**
* Indicates that a module has not been installed yet.
......@@ -376,24 +376,17 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents
$module_list = array_keys($module_list);
}
$storage = new DatabaseStorage();
foreach ($module_list as $module) {
// Uninstall the module.
module_load_install($module);
module_invoke($module, 'uninstall');
drupal_uninstall_schema($module);
// Remove any stray configuration settings.
// Get the names of default configurations provided by this module
// by scanning its config directory.
$module_config_dir = drupal_get_path('module', $module) . '/config';
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();
}
// Remove all configuration belonging to the module.
$config_names = $storage->listAll($module . '.');
foreach ($config_names as $config_name) {
config($config_name)->delete();
}
watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
......
......@@ -487,7 +487,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
$versions = drupal_get_schema_versions($module);
$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);
// If the module has no current updates, but has some that were
......
......@@ -10,6 +10,7 @@
use Drupal\Component\Graph\Graph;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\ConfigException;
/**
* 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) {
$module = strtok($config_name, '.');
// Load and set default configuration values.
// Throws a FileStorageReadException if there is no default configuration
// file, which is required to exist.
$file = new FileStorage($config_name);
$file->setPath(drupal_get_path('module', $module) . '/config');
$default_data = $file->read();
$file = new FileStorage(array('directory' => drupal_get_path('module', $module) . '/config'));
if (!$file->exists($config_name)) {
throw new ConfigException("Default configuration file $config_name for $module extension not found but is required to exist.");
}
$default_data = $file->read($config_name);
// Merge any possibly existing original data into default values.
// Only relevant when being called repetitively on the same config object.
......
<?php
namespace Drupal\Core\Config;
/**
* @file
* Definition of Drupal\Core\Config\Config.
*/
use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Config\ConfigException;
namespace Drupal\Core\Config;
/**
* Represents the default configuration storage object.
* Defines the default configuration object.
*/
class DrupalConfig {
class Config {
/**
* The storage engine to save this config object to.
* The name of the configuration object.
*
* @var StorageInterface
* @var string
*/
protected $storage;
protected $name;
/**
* The data of the configuration object.
......@@ -25,38 +27,36 @@ class DrupalConfig {
protected $data = array();
/**
* Constructs a DrupalConfig object.
* The injected storage dispatcher object.
*
* @param StorageInterface $storage
* The storage engine where this config object should be saved.
* @var Drupal\Core\Config\StorageDispatcher
*/
protected $storageDispatcher;
/**
* Constructs a configuration object.
*
* @todo $this should really know about $name and make it publicly accessible.
* @param Drupal\Core\Config\StorageDispatcher $storageDispatcher
* A storage dispatcher object to use for reading and writing the
* configuration data.
*/
public function __construct(StorageInterface $storage) {
$this->storage = $storage;
$this->read();
public function __construct(StorageDispatcher $storageDispatcher) {
$this->storageDispatcher = $storageDispatcher;
}
/**
* Reads config data from the active store into our object.
* Returns the name of this configuration object.
*/
public function read() {
$data = $this->storage->read();
$this->setData($data !== FALSE ? $data : array());
return $this;
public function getName() {
return $this->name;
}
/**
* Checks whether a particular value is overridden.
*
* @param $key
* @todo
*
* @return
* @todo
* Sets the name of this configuration object.
*/
public function isOverridden($key) {
return isset($this->_overrides[$key]);
public function setName($name) {
$this->name = $name;
return $this;
}
/**
......@@ -89,7 +89,7 @@ public function isOverridden($key) {
public function get($key = '') {
global $conf;
$name = $this->storage->getName();
$name = $this->getName();
if (isset($conf[$name])) {
$merged_data = drupal_array_merge_deep($this->data, $conf[$name]);
}
......@@ -201,13 +201,48 @@ public function clear($key) {
else {
drupal_array_unset_nested_value($this->data, $parts);
}
return $this;
}
/**
* Loads configuration data into this object.
*/
public function load() {
$this->setData(array());
$data = $this->storageDispatcher->selectStorage('read', $this->name)->read($this->name);
if ($data !== FALSE) {
$this->setData($data);
}
return $this;
}
/**
* Saves the configuration object.
*/
public function save() {
$this->storage->write($this->data);
$this->sortByKey($this->data);
$this->storageDispatcher->selectStorage('write', $this->name)->write($this->name, $this->data);
return $this;
}
/**
* Sorts all keys in configuration data.
*
* Ensures that re-inserted keys appear in the same location as before, in
* order to ensure an identical order regardless of storage controller.
* A consistent order is important for any storage that allows any kind of
* diff operation.
*
* @param array $data
* An associative array to sort recursively by key name.
*/
public function sortByKey(array &$data) {
ksort($data);
foreach ($data as &$value) {
if (is_array($value)) {
$this->sortByKey($value);
}
}
}
/**
......@@ -215,6 +250,7 @@ public function save() {
*/
public function delete() {
$this->data = array();
$this->storage->delete();
$this->storageDispatcher->selectStorage('write', $this->name)->delete($this->name);
return $this;
}
}
<?php
/**
* @file
* Definition of Drupal\Core\Config\ConfigException.
*/
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
/**
* @file
* Definition of Drupal\Core\Config\ConfigFactory.
*/
namespace Drupal\Core\Config;
/**
* Defines the configuration object factory.
*
* The configuration object factory instantiates a Config object for each
* configuration object name that is accessed and returns it to callers.
*
* @see Drupal\Core\Config\Config
*
* Each configuration object gets a storage dispatcher object injected, which
* determines the storage controller to use for reading and writing the
* configuration data.
*
* @see Drupal\Core\Config\StorageDispatcher
*/
class ConfigFactory {
/**
* A storage dispatcher instance to use for reading and writing configuration data.
*
* @var Drupal\Core\Config\StorageDispatcher
*/
protected $storageDispatcher;
/**
* Constructs the Config factory.
*
* @param Drupal\Core\Config\StorageDispatcher $storage_dispatcher
* The storage dispatcher object to use for reading and writing
* configuration data.
*/
public function __construct(StorageDispatcher $storage_dispatcher) {
$this->storageDispatcher = $storage_dispatcher;
}
/**
* Returns a configuration object for a given name.
*
* @param string $name
* The name of the configuration object to construct.
*
* @return Drupal\Core\Config\Config
* A configuration object with the given $name.
*/
public function get($name) {
// @todo Caching the instantiated objects per name might cut off a fair
// amount of CPU time and memory. Only the data within the configuration
// object changes, so the additional cost of instantiating duplicate
// objects could possibly be avoided. It is not uncommon for a
// configuration object to be retrieved many times during a single
// request; e.g., 'system.performance' alone is retrieved around 10-20
// times within a single page request. Sub-requests via HttpKernel will
// most likely only increase these counts.
// @todo Benchmarks were performed with a script that essentially retained
// all instantiated configuration objects in memory until script execution
// ended. A variant of that script called config() within a helper
// function only, which inherently meant that PHP destroyed all
// configuration objects after leaving the function. Consequently,
// benchmark results looked entirely different. Profiling should probably
// redone under more realistic conditions; e.g., actual HTTP requests.
// @todo The decrease of CPU time is interesting, since that means that
// ContainerBuilder involves plenty of function calls (which are known to
// be slow in PHP).
$config = new Config($this->storageDispatcher);
return $config->setName($name);
}
}
<?php
/**
* @file
* Definition of Drupal\Core\Config\DatabaseStorage.
*/
namespace Drupal\Core\Config;
use Drupal\Core\Config\StorageBase;
use Drupal\Core\Database\Database;
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
// read without actually having the database available. In this case,
// catch the exception and just return an empty array so the caller can
// handle it if need be.
$data = array();
// @todo Remove this and use appropriate StorageDispatcher configuration in
// the installer instead.
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) {
$data = $this->decode($raw);
}
......@@ -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);
return db_merge('config')
->key(array('name' => $this->name))
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
return (bool) $this->getConnection()->merge('config', $options)
->key(array('name' => $name))
->fields(array('data' => $data))
->execute();
}
/**
* @todo
* Implements Drupal\Core\Config\StorageInterface::delete().
*
* @throws PDOException
*
* @todo Ignore slave targets for data manipulation operations.
*/
public function deleteFromActive() {
db_delete('config')
->condition('name', $this->name)
public function delete($name) {
$options = array('return' => Database::RETURN_AFFECTED) + $this->options;
return (bool) $this->getConnection()->delete('config', $options)
->condition('name', $name)
->execute();
}
/**
* Implements StorageInterface::encode().
* Implements Drupal\Core\Config\StorageInterface::encode().
*/
public static function encode($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) {
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 = '') {
return db_query('SELECT name FROM {config} WHERE name LIKE :name', array(':name' => db_like($prefix) . '%'))->fetchCol();
public function listAll($prefix = '') {
return $this->getConnection()->query('SELECT name FROM {config} WHERE name LIKE :name', array(
':name' => db_like($prefix) . '%',
), $this->options)->fetchCol();
}
}
<?php
/**
* @file
* Definition of Drupal\Core\Config\FileStorage.
*/
namespace Drupal\Core\Config;
use Symfony\Component\Yaml\Yaml;
/**
* Represents the file storage controller.
*
* @todo Implement StorageInterface after removing DrupalConfig methods.
* @todo Consider to extend StorageBase.
* Defines the file storage controller.
*/
class FileStorage {
class FileStorage implements StorageInterface {
/**
* The name of the configuration object.
* Configuration options for this storage controller.
*
* @var string
*/
protected $name;
/**
* The filesystem path containing the configuration object.
* - directory: The filesystem path for configuration objects.
*
* @var string