diff --git a/core/includes/config.inc b/core/includes/config.inc index 8d0eea1..7b29cc5 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -1,7 +1,9 @@ $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); - $file_storage = new FileStorage($config_name); - $file_storage->setPath($module_config_dir); - $database_storage->write($file_storage->read()); - } - } + $importer = new StorageManager( + new FileStorage(array('directory' => $module_config_dir)), // Source storage + new DatabaseStorage() // Destination storage + ); + return $importer->import(); } /** * @todo http://drupal.org/node/1552396 renames this into config_load_all(). */ function config_get_storage_names_with_prefix($prefix = '') { - return DatabaseStorage::getNamesWithPrefix($prefix); + $storage = new DatabaseStorage(); + return $storage->getNamesWithPrefix($prefix); } /** @@ -73,16 +61,22 @@ 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. + * @param string $class + * (optional) The name of the class to use for the configuration object. + * Defaults to Drupal\Core\Config\ConfigObject. * - * @return + * @return Drupal\Core\Config\ConfigObject * An instance of the class specified in the $class parameter. - * - * @todo Replace this with an appropriate factory / ability to inject in - * alternate storage engines.. */ -function config($name, $class = 'Drupal\Core\Config\DrupalConfig') { - return new $class(new DatabaseStorage($name)); +function config($name, $class = 'Drupal\Core\Config\ConfigObject') { + // @todo Factory needs to be instantiated to prevent statics from being shared + // with parent environment in tests. Replace with DI container. + $factory = &drupal_static(__FUNCTION__); + + if (!isset($factory)) { + $factory = new ConfigFactory(new StorageManager(new DatabaseStorage())); + } + // @todo Do not reload an already loaded config object. + // @todo Make it possible to skip load for known to be new config objects? + return $factory->get($name, $class)->load(); } diff --git a/core/includes/install.inc b/core/includes/install.inc index 96e84b3..546a247 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -2,6 +2,8 @@ use Drupal\Core\Database\Database; use Drupal\Core\Config\FileStorage; +use Drupal\Core\Config\DatabaseStorage; +use Drupal\Core\Config\StorageManager; /** * Indicates that a module has not been installed yet. @@ -376,6 +378,7 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents $module_list = array_keys($module_list); } + $storage_manager = new StorageManager(NULL, new DatabaseStorage()); foreach ($module_list as $module) { // Uninstall the module. module_load_install($module); @@ -385,16 +388,9 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents // 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(); - } - } + $storage_manager + ->setSource(new FileStorage(array('directory' => drupal_get_path('module', $module) . '/config'))) + ->uninstall(); watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO); drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED); diff --git a/core/includes/update.inc b/core/includes/update.inc index 49df15b..3bdacd2 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -944,9 +944,8 @@ function update_variables_to_config($config_name, array $variable_map) { // 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')); + $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. diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php new file mode 100644 index 0000000..0deabb6 --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -0,0 +1,72 @@ +storageManager = $storageManager; + } + + /** + * Returns a configuration object for a given name. + * + * Configuration objects are instantiated once only. + * + * @param string $name + * The name of the configuration object to construct. + * @param string $class + * (optional) The name of the class to use for the configuration object. + * Defaults to Drupal\Core\Config\ConfigObject. + * @param string $manager_class + * (optional) The name of the storage manager class to use. Defaults to + * Drupal\Core\Config\StorageManager. + * + * @return Drupal\Core\Config\ConfigObject + * A configuration object with the given $name. + */ + public function get($name, $class = 'Drupal\Core\Config\ConfigObject') { + // Instantiate the configuration object + if (!isset($this->configObjects[$name][$class])) { + $this->configObjects[$name][$class] = new $class($name, $this->storageManager); + } + return $this->configObjects[$name][$class]; + } +} diff --git a/core/lib/Drupal/Core/Config/ConfigObject.php b/core/lib/Drupal/Core/Config/ConfigObject.php new file mode 100644 index 0000000..745eb9c --- /dev/null +++ b/core/lib/Drupal/Core/Config/ConfigObject.php @@ -0,0 +1,229 @@ +setName($name); + $this->storageManager = $storageManager; + } + + /** + * Returns the name of this configuration object. + */ + public function getName() { + return $this->name; + } + + /** + * Sets the name of this configuration object. + */ + public function setName($name) { + $this->name = $name; + return $this; + } + + /** + * 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->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 { + drupal_array_set_nested_value($this->data, $parts, $value); + } + return $this; + } + + /** + * Casts a saved value to a string. + * + * The configuration system only saves strings or arrays. Any scalar + * non-string value is cast to a string. The one exception is boolean FALSE + * which would normally become '' when cast to a string, but is manually + * cast to '0' here for convenience and consistency. + * + * Any non-scalar value that is not an array (aka objects) gets cast + * to an array. + * + * @param $value + * A value being saved into the configuration system. + * @param $value + * The value cast to a string or array. + */ + public function castValue($value) { + if (is_scalar($value)) { + // Handle special case of FALSE, which should be '0' instead of ''. + if ($value === FALSE) { + $value = '0'; + } + else { + $value = (string) $value; + } + } + else { + // Any non-scalar value must be an array. + if (!is_array($value)) { + $value = (array) $value; + } + // Recurse into any nested keys. + foreach ($value as $key => $nested_value) { + $value[$key] = $this->castValue($nested_value); + } + } + return $value; + } + + /** + * Unsets value in this config object. + * + * @param $key + * Name of the key whose value should be unset. + */ + public function clear($key) { + $parts = explode('.', $key); + if (count($parts) == 1) { + unset($this->data[$key]); + } + else { + drupal_array_unset_nested_value($this->data, $parts); + } + return $this; + } + + /** + * Loads configuration data into this object. + */ + public function load() { + $data = $this->storageManager->getSource()->read($this->name); + $this->setData($data !== FALSE ? $data : array()); + return $this; + } + + /** + * Saves the configuration object. + */ + public function save() { + $this->storageManager->getDestination()->write($this->name, $this->data); + return $this; + } + + /** + * Deletes the configuration object. + */ + public function delete() { + $this->data = array(); + $this->storageManager->getDestination()->delete($this->name); + return $this; + } +} diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index c736245..d43b65e 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -2,25 +2,52 @@ namespace Drupal\Core\Config; -use Drupal\Core\Config\StorageBase; +use Drupal\Core\Config\StorageInterface; +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. + * + * - target: The connection to use for storage operations. + * + * @var array + */ + protected $options; + + /** + * Implements StorageInterface::__construct(). + */ + public function __construct(array $info = array()) { + $info += array( + 'target' => 'default', + ); + $this->options = $info; + } + + /** + * Returns the database connection to use. + */ + protected function getConnection() { + return Database::getConnection($this->options['target']); + } /** * Implements StorageInterface::read(). */ - public function read() { + public function read($name) { // 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(); 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,22 +58,22 @@ class DatabaseStorage extends StorageBase { } /** - * Implements StorageInterface::writeToActive(). + * Implements StorageInterface::write(). */ - public function writeToActive($data) { + public function write($name, array $data) { $data = $this->encode($data); - return db_merge('config') - ->key(array('name' => $this->name)) + return $this->getConnection()->merge('config', $this->options) + ->key(array('name' => $name)) ->fields(array('data' => $data)) ->execute(); } /** - * @todo + * Implements StorageInterface::delete(). */ - public function deleteFromActive() { - db_delete('config') - ->condition('name', $this->name) + public function delete($name) { + $this->getConnection()->delete('config', $this->options) + ->condition('name', $name) ->execute(); } @@ -67,7 +94,7 @@ class DatabaseStorage extends StorageBase { /** * Implements StorageInterface::getNamesWithPrefix(). */ - static public function getNamesWithPrefix($prefix = '') { + public function getNamesWithPrefix($prefix = '') { return db_query('SELECT name FROM {config} WHERE name LIKE :name', array(':name' => db_like($prefix) . '%'))->fetchCol(); } } diff --git a/core/lib/Drupal/Core/Config/DrupalConfig.php b/core/lib/Drupal/Core/Config/DrupalConfig.php deleted file mode 100644 index f5a9220..0000000 --- a/core/lib/Drupal/Core/Config/DrupalConfig.php +++ /dev/null @@ -1,220 +0,0 @@ -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 { - drupal_array_set_nested_value($this->data, $parts, $value); - } - return $this; - } - - /** - * Casts a saved value to a string. - * - * The configuration system only saves strings or arrays. Any scalar - * non-string value is cast to a string. The one exception is boolean FALSE - * which would normally become '' when cast to a string, but is manually - * cast to '0' here for convenience and consistency. - * - * Any non-scalar value that is not an array (aka objects) gets cast - * to an array. - * - * @param $value - * A value being saved into the configuration system. - * @param $value - * The value cast to a string or array. - */ - public function castValue($value) { - if (is_scalar($value)) { - // Handle special case of FALSE, which should be '0' instead of ''. - if ($value === FALSE) { - $value = '0'; - } - else { - $value = (string) $value; - } - } - else { - // Any non-scalar value must be an array. - if (!is_array($value)) { - $value = (array) $value; - } - // Recurse into any nested keys. - foreach ($value as $key => $nested_value) { - $value[$key] = $this->castValue($nested_value); - } - } - return $value; - } - - /** - * Unsets value in this config object. - * - * @param $key - * Name of the key whose value should be unset. - */ - public function clear($key) { - $parts = explode('.', $key); - if (count($parts) == 1) { - unset($this->data[$key]); - } - else { - drupal_array_unset_nested_value($this->data, $parts); - } - } - - /** - * Saves the configuration object. - */ - public function save() { - $this->storage->write($this->data); - } - - /** - * Deletes the configuration object. - */ - public function delete() { - $this->data = array(); - $this->storage->delete(); - } -} diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index 2a6d448..dd55c14 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -2,58 +2,31 @@ namespace Drupal\Core\Config; +use Drupal\Core\Config\StorageInterface; 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 + * @var array */ - protected $path; + protected $info; /** * Implements StorageInterface::__construct(). */ - public function __construct($name = NULL) { - $this->name = $name; - } - - /** - * Returns the path containing the configuration file. - * - * @return string - * The relative path to the configuration object. - */ - public function getPath() { - // If the path has not been set yet, retrieve and assign the default path - // for configuration files. - if (!isset($this->path)) { - $this->setPath(config_get_config_directory()); + public function __construct(array $info = array()) { + if (!isset($info['directory'])) { + $info['directory'] = config_get_config_directory(); } - return $this->path; - } - - /** - * Sets the path containing the configuration file. - */ - public function setPath($directory) { - $this->path = $directory; - return $this; + $this->info = $info; } /** @@ -62,8 +35,8 @@ class FileStorage { * @return string * The path to the configuration file. */ - public function getFilePath() { - return $this->getPath() . '/' . $this->getName() . '.' . self::getFileExtension(); + public function getFilePath($name) { + return $this->info['directory'] . '/' . $name . '.' . self::getFileExtension(); } /** @@ -82,8 +55,8 @@ class FileStorage { * @return bool * TRUE if the configuration file exists, FALSE otherwise. */ - protected function exists() { - return file_exists($this->getFilePath()); + public function exists($name) { + return file_exists($this->getFilePath($name)); } /** @@ -91,10 +64,10 @@ class FileStorage { * * @throws FileStorageException */ - public function write($data) { + public function write($name, array $data) { $data = $this->encode($data); - if (!file_put_contents($this->getFilePath(), $data)) { - throw new FileStorageException('Failed to write configuration file: ' . $this->getFilePath()); + if (!file_put_contents($this->getFilePath($name), $data)) { + throw new FileStorageException('Failed to write configuration file: ' . $this->getFilePath($name)); } } @@ -103,15 +76,15 @@ class FileStorage { * * @throws FileStorageReadException */ - public function read() { - if (!$this->exists()) { - throw new FileStorageReadException("Configuration file '$this->name' does not exist."); + public function read($name) { + if (!$this->exists($name)) { + throw new FileStorageReadException("Configuration file '$name' does not exist."); } - $data = file_get_contents($this->getFilePath()); + $data = file_get_contents($this->getFilePath($name)); $data = $this->decode($data); if ($data === FALSE) { - throw new FileStorageReadException("Failed to decode configuration file '$this->name'."); + throw new FileStorageReadException("Failed to decode configuration file '$name'."); } return $data; } @@ -119,9 +92,9 @@ class FileStorage { /** * Deletes a configuration file. */ - public function delete() { + public function delete($name) { // Needs error handling and etc. - @drupal_unlink($this->getFilePath()); + @drupal_unlink($this->getFilePath($name)); } /** @@ -134,7 +107,16 @@ class FileStorage { } /** - * Implements StorageInterface::decode(). + * Decodes configuration data from the storage-specific format. + * + * @param string $raw + * The raw configuration data string to decode. + * + * @return array + * The decoded configuration data as an associative array. + * + * This is a publicly accessible static method to allow for alternative + * usages in data conversion scripts and also tests. */ public static function decode($raw) { if (empty($raw)) { @@ -144,29 +126,24 @@ class FileStorage { } /** - * Implements StorageInterface::getName(). - */ - public function getName() { - return $this->name; - } - - /** - * Implements StorageInterface::setName(). + * Implements StorageInterface::getNamesWithPrefix(). + * + * @todo Allow to search for files in custom paths. */ - public function setName($name) { - $this->name = $name; + public function getNamesWithPrefix($prefix = '') { + if (is_dir($this->info['directory'])) { + $files = glob($this->info['directory'] . '/' . $prefix . '*.' . $this->getFileExtension()); + return array_map(array($this, 'cleanFileName'), $files); + } + else { + return array(); + } } /** - * Implements StorageInterface::getNamesWithPrefix(). + * Helper function to get file name without extension. */ - public static function getNamesWithPrefix($prefix = '') { - // @todo Use $this->getPath() to allow for contextual search of files in - // custom paths. - $files = glob(config_get_config_directory() . '/' . $prefix . '*.' . FileStorage::getFileExtension()); - $clean_name = function ($value) { - return basename($value, '.' . FileStorage::getFileExtension()); - }; - return array_map($clean_name, $files); + public function cleanFileName($filename) { + return basename($filename, '.' . $this->getFileExtension()); } } diff --git a/core/lib/Drupal/Core/Config/StorageBase.php b/core/lib/Drupal/Core/Config/StorageBase.php deleted file mode 100644 index b03ff27..0000000 --- a/core/lib/Drupal/Core/Config/StorageBase.php +++ /dev/null @@ -1,121 +0,0 @@ -name = $name; - } - - /** - * Instantiates a new file storage object or returns the existing one. - * - * @return Drupal\Core\Config\FileStorage - * The file object for this configuration object. - */ - protected function fileStorage() { - if (!isset($this->fileStorage)) { - $this->fileStorage = new FileStorage($this->name); - } - return $this->fileStorage; - } - - /** - * Implements StorageInterface::copyToFile(). - */ - public function copyToFile() { - return $this->writeToFile($this->read()); - } - - /** - * Implements StorageInterface::deleteFile(). - */ - public function deleteFile() { - return $this->fileStorage()->delete(); - } - - /** - * Implements StorageInterface::copyFromFile(). - */ - public function copyFromFile() { - return $this->writeToActive($this->readFromFile()); - } - - /** - * @todo - * - * @return - * @todo - */ - public function readFromFile() { - return $this->fileStorage()->read($this->name); - } - - /** - * Implements StorageInterface::isOutOfSync(). - */ - public function isOutOfSync() { - return $this->read() !== $this->readFromFile(); - } - - /** - * Implements StorageInterface::write(). - */ - public function write($data) { - $this->writeToActive($data); - $this->writeToFile($data); - } - - /** - * Implements StorageInterface::writeToFile(). - */ - public function writeToFile($data) { - return $this->fileStorage()->write($data); - } - - /** - * Implements StorageInterface::delete(). - */ - public function delete() { - $this->deleteFromActive(); - $this->deleteFile(); - } - - /** - * Implements StorageInterface::getName(). - */ - public function getName() { - return $this->name; - } - - /** - * Implements StorageInterface::setName(). - */ - public function setName($name) { - $this->name = $name; - } -} diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index 43141a5..764e968 100644 --- a/core/lib/Drupal/Core/Config/StorageInterface.php +++ b/core/lib/Drupal/Core/Config/StorageInterface.php @@ -7,74 +7,43 @@ namespace Drupal\Core\Config; * * Classes implementing this interface allow reading and writing configuration * data from and to the storage. - * - * @todo Remove all active/file methods. They belong onto DrupalConfig only. */ interface StorageInterface { /** - * Constructs a storage manipulation class. + * Constructs the storage controller. * - * @param string $name - * (optional) The name of a configuration object to load. - */ - function __construct($name = NULL); - - /** - * Reads the configuration data from the storage. - */ - function read(); - - /** - * Copies the configuration data from the storage into a file. + * @param array $info + * An associative array containing configuration options specific to the + * storage controller. */ - function copyToFile(); + public function __construct(array $info = array()); /** - * Copies the configuration data from the file into the storage. - */ - function copyFromFile(); - - /** - * Deletes the configuration data file. - */ - function deleteFile(); - - /** - * Checks whether the file and the storage is in sync. + * Reads configuration data from the storage. * - * @return - * TRUE if the file and the storage contains the same data, FALSE - * if not. + * @param string $name + * The name of a configuration object to load. */ - function isOutOfSync(); + public function read($name); /** - * Writes the configuration data into the active storage and the file. + * Writes configuration data to the storage. * - * @param $data + * @param string $name + * The name of a configuration object to save. + * @param array $data * The configuration data to write. */ - function write($data); - - /** - * Writes the configuration data into the active storage but not the file. - * - * Use this function if you need to make temporary changes to your - * configuration. - * - * @param $data - * The configuration data to write into active storage. - */ - function writeToActive($data); + public function write($name, array $data); /** - * Writes the configuration data into the file. + * Deletes a configuration object from the storage. * - * @param $data - * The configuration data to write into the file. + * @param string $name + * The name of a configuration object to delete. */ - function writeToFile($data); + public function delete($name); /** * Encodes configuration data into the storage-specific format. @@ -105,16 +74,6 @@ interface StorageInterface { public static function decode($raw); /** - * Gets the name of this object. - */ - public function getName(); - - /** - * Sets the name of this object. - */ - public function setName($name); - - /** * Gets configuration object names starting with a given prefix. * * Given the following configuration objects: @@ -131,5 +90,6 @@ interface StorageInterface { * @return array * An array containing matching configuration object names. */ - static function getNamesWithPrefix($prefix = ''); + function getNamesWithPrefix($prefix = ''); + } diff --git a/core/lib/Drupal/Core/Config/StorageManager.php b/core/lib/Drupal/Core/Config/StorageManager.php new file mode 100644 index 0000000..62e8fbb --- /dev/null +++ b/core/lib/Drupal/Core/Config/StorageManager.php @@ -0,0 +1,120 @@ +source = $source; + // If destination + $this->destination = isset($destination) ? $destination : $source; + } + + /** + * Returns a storage controller to use as a source (read operations). + */ + public function getSource() { + return $this->source; + } + + /** + * Sets a storage controller to be used as a source (read operations). + */ + public function setSource(StorageInterface $source) { + $this->source = $source; + return $this; + } + + /** + * Returns a storage controller to use as a destination (write operations). + */ + public function getDestination() { + return $this->destination; + } + + /** + * Sets a storage controller to be used as a destination (read operations). + */ + public function setDestination(StorageInterface $destination) { + $this->destination = $destination; + return $this; + } + + /** + * Copy configuration object from source to destination. + */ + public function copy($name) { + if ($data = $this->getSource()->read($name)) { + $this->getDestination()->write($name, $data); + } + return $this; + } + + /** + * Copy all configuration objects from source to destination. + * + * @return + * An array containing imported configuration object names. + */ + public function import() { + $result = array(); + foreach ($this->getSource()->getNamesWithPrefix() as $name) { + $this->copy($name); + $result[] = $name; + } + return $result; + } + + /** + * Delete all configuration objects from source to destination. + * + * @return + * An array containing imported configuration object names. + */ + public function uninstall() { + $result = array(); + foreach ($this->getSource()->getNamesWithPrefix() as $name) { + $this->getDestination()->delete($name); + $result[] = $name; + } + return $result; + } +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php new file mode 100644 index 0000000..5e5b926 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php @@ -0,0 +1,88 @@ + 'CRUD operations', + 'description' => 'Tests CRUD operations on configuration objects.', + 'group' => 'Configuration', + ); + } + + /** + * Tests CRUD operations. + */ + function testCRUD() { + $storage = new DatabaseStorage(); + $name = 'config_test.crud'; + + // Create a new configuration object. + $config = config($name); + $config->set('value', 'initial'); + $config->save(); + + // Verify the active store contains the saved value. + $actual_data = $storage->read($name); + $this->assertIdentical($actual_data, array('value' => 'initial')); + + // Update the configuration object instance. + $config->set('value', 'instance-update'); + $config->save(); + + // Verify the active store contains the updated value. + $actual_data = $storage->read($name); + $this->assertIdentical($actual_data, array('value' => 'instance-update')); + + // Verify a call to config() immediately returns the updated value. + $new_config = config($name); + $this->assertIdentical($new_config->get(), $config->get()); + + // Verify config() returned the existing config instance. + $this->assertIdentical($new_config, $config); + + // Delete the configuration object. + $config->delete(); + + // Verify the configuration object is empty. + $this->assertIdentical($config->get(), array()); + + // Verify the active store contains no value. + $actual_data = $storage->read($name); + $this->assertIdentical($actual_data, array()); + + // Verify config() returns no data. + $new_config = config($name); + $this->assertIdentical($new_config->get(), $config->get()); + + // Verify config() returned the existing config instance. + $this->assertIdentical($new_config, $config); + + // Re-create the configuration object. + $config->set('value', 're-created'); + $config->save(); + + // Verify the active store contains the updated value. + $actual_data = $storage->read($name); + $this->assertIdentical($actual_data, array('value' => 're-created')); + + // Verify a call to config() immediately returns the updated value. + $new_config = config($name); + $this->assertIdentical($new_config->get(), $config->get()); + + // Verify config() returned the existing config instance. + $this->assertIdentical($new_config, $config); + } +} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php index bfd27ac..73c4f97 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigFileContentTest.php @@ -7,6 +7,7 @@ namespace Drupal\config\Tests; +use Drupal\Core\Config\DatabaseStorage; use Drupal\Core\Config\FileStorage; use Drupal\simpletest\WebTestBase; @@ -34,7 +35,6 @@ class ConfigFileContentTest extends WebTestBase { * Tests setting, writing, and reading of a configuration setting. */ function testReadWriteConfig() { - $config_dir = config_get_config_directory(); $name = 'foo.bar'; $key = 'foo'; $value = 'bar'; @@ -71,7 +71,6 @@ class ConfigFileContentTest extends WebTestBase { // Verify nothing was saved. $db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $name))->fetch(); $this->assertIdentical($db_config, FALSE, t('Active store does not have a record for %name', array('%name' => $name))); - $this->assertFalse(file_exists($config_dir . '/' . $name . '.' . $this->fileExtension), 'Configuration file does not exist.'); // Add a top level value $config = config($name); @@ -100,9 +99,6 @@ class ConfigFileContentTest extends WebTestBase { $db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $name))->fetch(); $this->assertEqual($db_config->name, $name, t('After saving configuration, active store has a record for %name', array('%name' => $name))); - // Verify the file exists. - $this->assertTrue(file_exists($config_dir . '/' . $name . '.' . $this->fileExtension), t('After saving configuration, config file exists.')); - // Read top level value $config = config($name); // $this->assertEqual($config->name, $name); @@ -161,27 +157,25 @@ class ConfigFileContentTest extends WebTestBase { $db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $chained_name))->fetch(); $this->assertEqual($db_config->name, $chained_name, t('After saving configuration by chaining through set(), active store has a record for %name', array('%name' => $chained_name))); - // Verify the file exists from a chained save. - $this->assertTrue(file_exists($config_dir . '/' . $chained_name . '.' . $this->fileExtension), t('After saving configuration by chaining through set(), config file exists.')); - // Get file listing for all files starting with 'foo'. Should return // two elements. - $files = FileStorage::getNamesWithPrefix('foo'); + $database_storage = new DatabaseStorage(); + $files = $database_storage->getNamesWithPrefix('foo'); $this->assertEqual(count($files), 2, 'Two files listed with the prefix \'foo\'.'); // Get file listing for all files starting with 'biff'. Should return // one element. - $files = FileStorage::getNamesWithPrefix('biff'); + $files = $database_storage->getNamesWithPrefix('biff'); $this->assertEqual(count($files), 1, 'One file listed with the prefix \'biff\'.'); // Get file listing for all files starting with 'foo.bar'. Should return // one element. - $files = FileStorage::getNamesWithPrefix('foo.bar'); + $files = $database_storage->getNamesWithPrefix('foo.bar'); $this->assertEqual(count($files), 1, 'One file listed with the prefix \'foo.bar\'.'); // Get file listing for all files starting with 'bar'. Should return // an empty array. - $files = FileStorage::getNamesWithPrefix('bar'); + $files = $database_storage->getNamesWithPrefix('bar'); $this->assertEqual($files, array(), 'No files listed with the prefix \'bar\'.'); // Delete the configuration. @@ -191,9 +185,6 @@ class ConfigFileContentTest extends WebTestBase { // Verify the database entry no longer exists. $db_config = db_query('SELECT * FROM {config} WHERE name = :name', array(':name' => $name))->fetch(); $this->assertIdentical($db_config, FALSE); - $this->assertFalse(file_exists($config_dir . '/' . $name . $this->fileExtension)); - - // Attempt to delete non-existing configuration. } /** @@ -216,17 +207,10 @@ class ConfigFileContentTest extends WebTestBase { 'invalid xml' => ' & < > " \' ', ); - // Attempt to read non-existing configuration. - $config = config($name); - - foreach ($config_data as $key => $value) { - $config->set($key, $value); - } - - $config->save(); - - $config_filestorage = new FileStorage($name); - $config_parsed = $config_filestorage->read(); + // Encode and write, and reload and decode the configuration data. + $filestorage = new FileStorage(); + $filestorage->write($name, $config_data); + $config_parsed = $filestorage->read($name); $key = 'numeric keys'; $this->assertIdentical($config_data[$key], $config_parsed[$key]); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigFileSecurityTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigFileSecurityTest.php index 5f9ec07..ae80fb2 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigFileSecurityTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigFileSecurityTest.php @@ -30,17 +30,14 @@ class ConfigFileSecurityTest extends WebTestBase { * Tests that a file written by this system can be successfully read back. */ function testFilePersist() { - $file = new FileStorage($this->filename); - $file->write($this->testContent); - - unset($file); + $file = new FileStorage(); + $file->write($this->filename, $this->testContent); // Reading should throw an exception in case of bad validation. // Note that if any other exception is thrown, we let the test system // handle catching and reporting it. try { - $file = new FileStorage($this->filename); - $saved_content = $file->read(); + $saved_content = $file->read($this->filename); $this->assertEqual($saved_content, $this->testContent); }