diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 7daf311..5d09225 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -2437,26 +2437,37 @@ function drupal_container(Container $reset = NULL) { // This will get merged with the full Kernel-built Container on normal page // requests. $container = new ContainerBuilder(); - // Register configuration storage dispatcher. - $container->setParameter('config.storage.info', array( - 'Drupal\Core\Config\DatabaseStorage' => array( - 'connection' => 'default', - 'target' => 'default', - 'read' => TRUE, - 'write' => TRUE, - ), + + // Register configuration storage class and options. + // @todo The active store and its options need to be configurable. + // Use either global $conf (recursion warning) or global $config, or a + // bootstrap configuration *file* to allow to set/override this very + // lowest of low level configuration. + $container->setParameter('config.storage.options', array( 'Drupal\Core\Config\FileStorage' => array( 'directory' => config_get_config_directory(), - 'read' => TRUE, - 'write' => FALSE, + ), + 'Drupal\Core\Config\CacheStorage' => array( + 'backend' => 'Drupal\Core\Cache\DatabaseBackend', + // @todo Add and replace with 'config' default bin. + 'bin' => 'cache', ), )); - $container->register('config.storage.dispatcher', 'Drupal\Core\Config\StorageDispatcher') - ->addArgument('%config.storage.info%'); + $container->register('config.storage', 'Drupal\Core\Config\CachedFileStorage') + ->addArgument('%config.storage.options%'); // Register configuration object factory. $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory') - ->addArgument(new Reference('config.storage.dispatcher')); + ->addArgument(new Reference('config.storage')); + + // Register configuration state. + $container->setParameter('config.state.options', array( + 'connection' => 'default', + 'target' => 'default', + 'table' => 'config', + )); + $container->register('config.state', 'Drupal\Core\Config\DatabaseStorage') + ->addArgument('%config.state.options%'); } return $container; } diff --git a/core/includes/config.inc b/core/includes/config.inc index 5b1fe21..14f41f9 100644 --- a/core/includes/config.inc +++ b/core/includes/config.inc @@ -1,7 +1,6 @@ $config_dir)); - $target_storage = new DatabaseStorage(); + $target_storage = drupal_container()->get('config.storage'); $null_storage = new NullStorage(); // Upon installation, only new config objects need to be created. @@ -44,11 +40,12 @@ function config_install_default_config($type, $name) { } /** - * @todo Modules need a way to access the active store, whatever it is. + * Gets configuration object names starting with a given prefix. + * + * @see Drupal\Core\Config\StorageInterface::listAll() */ function config_get_storage_names_with_prefix($prefix = '') { - $storage = new DatabaseStorage(); - return $storage->listAll($prefix); + return drupal_container()->get('config.storage')->listAll($prefix); } /** @@ -131,17 +128,16 @@ function config_sync_changes(array $config_changes, StorageInterface $source_sto } /** - * Imports configuration from FileStorage to DatabaseStorage. + * Imports configuration into the active store. * * @return bool|null * TRUE if configuration was imported successfully, FALSE in case of a * synchronization error, or NULL if there are no changes to synchronize. */ function config_import() { - // Retrieve a list of differences between FileStorage and DatabaseStorage. - // @todo Leverage DI + config.storage.info. - $source_storage = new FileStorage(); - $target_storage = new DatabaseStorage(); + // Retrieve a list of differences between last known state and active store. + $source_storage = drupal_container()->get('config.state'); + $target_storage = drupal_container()->get('config.storage'); $config_changes = config_sync_get_changes($source_storage, $target_storage); if (empty($config_changes)) { @@ -185,8 +181,6 @@ function config_import() { * @todo Add support for other extension types; e.g., themes etc. */ function config_import_invoke_owner(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) { - $storage_dispatcher = drupal_container()->get('config.storage.dispatcher'); - // Allow modules to take over configuration change operations for // higher-level configuration data. // First pass deleted, then new, and lastly changed configuration, in order to @@ -199,13 +193,13 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou // handle the configuration change. $handled_by_module = FALSE; if (module_hook($module, 'config_import_' . $op)) { - $old_config = new Config($storage_dispatcher); + $old_config = new Config($target_storage); $old_config ->setName($name) ->load(); $data = $source_storage->read($name); - $new_config = new Config($storage_dispatcher); + $new_config = new Config($target_storage); $new_config->setName($name); if ($data !== FALSE) { $new_config->setData($data); @@ -222,13 +216,14 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou } /** - * Exports configuration from DatabaseStorage to FileStorage. + * Updates the last known state with the active store configuration. + * + * @todo config_export() is a misnomer now. Rename to config_state_update(). */ function config_export() { - // Retrieve a list of differences between DatabaseStorage and FileStorage. - // @todo Leverage DI + config.storage.info. - $source_storage = new DatabaseStorage(); - $target_storage = new FileStorage(); + // Retrieve a list of differences between active store and last known state. + $source_storage = drupal_container()->get('config.storage'); + $target_storage = drupal_container()->get('config.state'); $config_changes = config_sync_get_changes($source_storage, $target_storage); if (empty($config_changes)) { diff --git a/core/includes/module.inc b/core/includes/module.inc index ab45576..6c4f24d 100644 --- a/core/includes/module.inc +++ b/core/includes/module.inc @@ -6,7 +6,7 @@ */ use Drupal\Component\Graph\Graph; -use Drupal\Core\Config\DatabaseStorage; +use Drupal\Core\Config\NullStorage; /** * Load all the modules that have been enabled in the system table. @@ -622,19 +622,26 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE) $module_list = array_keys($module_list); } - $storage = new DatabaseStorage(); + $source_storage = new NullStorage(); + $target_storage = drupal_container()->get('config.storage'); foreach ($module_list as $module) { + // Remove all configuration belonging to the module. + $config_changes = $target_storage->listAll($module . '.'); + if (!empty($config_changes)) { + $config_changes = array( + 'delete' => $config_changes, + 'change' => array(), + 'create' => array(), + ); + $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage); + config_sync_changes($remaining_changes, $source_storage, $target_storage); + } + // Uninstall the module. module_load_install($module); module_invoke($module, 'uninstall'); drupal_uninstall_schema($module); - // 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); drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED); } diff --git a/core/lib/Drupal/Core/Config/CacheStorage.php b/core/lib/Drupal/Core/Config/CacheStorage.php new file mode 100644 index 0000000..6e77ae2 --- /dev/null +++ b/core/lib/Drupal/Core/Config/CacheStorage.php @@ -0,0 +1,112 @@ + 'Drupal\Core\Cache\DatabaseBackend', + 'bin' => 'config', + ); + $this->options = $options; + } + + /** + * Returns the instantiated Cache backend to use. + */ + protected function getBackend() { + if (!isset($this->storage)) { + $this->storage = new $this->options['backend']($this->options['bin']); + } + return $this->storage; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::exists(). + */ + public function exists($name) { + return (bool) $this->getBackend()->get($name); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::read(). + */ + public function read($name) { + if ($cache = $this->getBackend()->get($name)) { + return $cache->data; + } + return FALSE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::write(). + */ + public function write($name, array $data) { + $this->getBackend()->set($name, $data, CACHE_PERMANENT, array('config' => array($name))); + return TRUE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::delete(). + */ + public function delete($name) { + $this->getBackend()->delete($name); + return TRUE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::encode(). + */ + public static function encode($data) { + return serialize($data); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::decode(). + * + * @throws ErrorException + * unserialize() triggers E_NOTICE if the string cannot be unserialized. + */ + public static function decode($raw) { + $data = @unserialize($raw); + return is_array($data) ? $data : FALSE; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::listAll(). + * + * Not supported by CacheBackendInterface. + */ + public function listAll($prefix = '') { + return array(); + } +} diff --git a/core/lib/Drupal/Core/Config/CachedFileStorage.php b/core/lib/Drupal/Core/Config/CachedFileStorage.php new file mode 100644 index 0000000..7fbaef9 --- /dev/null +++ b/core/lib/Drupal/Core/Config/CachedFileStorage.php @@ -0,0 +1,125 @@ +options = $options; + + $this->storages['file'] = new FileStorage($options['Drupal\Core\Config\FileStorage']); + + unset($options['Drupal\Core\Config\FileStorage']); + list($active_class, $active_options) = each($options); + $this->storages['active'] = new $active_class($active_options); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::exists(). + */ + public function exists($name) { + // A single filestat is faster than a complex cache lookup and possibly + // subsequent filestat. + return $this->storages['file']->exists($name); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::read(). + */ + public function read($name) { + // Check the cache. + $data = $this->storages['active']->read($name); + // If the cache returns no result, check the file storage. + if ($data === FALSE) { + $data = $this->storages['file']->read($name); + // @todo Should the config object be cached if it does not exist? + if ($data !== FALSE) { + $this->storages['active']->write($name, $data); + } + } + return $data; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::write(). + */ + public function write($name, array $data) { + $success = $this->storages['file']->write($name, $data); + $this->storages['active']->delete($name); + return $success; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::delete(). + */ + public function delete($name) { + $success = TRUE; + foreach ($this->storages as $storage) { + if (!$storage->delete($name)) { + $success = FALSE; + } + } + return $success; + } + + /** + * Implements Drupal\Core\Config\StorageInterface::encode(). + * + * @todo Remove encode() from StorageInterface. + */ + public static function encode($data) { + return $this->storages['file']->encode($data); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::decode(). + * + * @todo Remove decode() from StorageInterface. + */ + public static function decode($raw) { + return $this->storages['file']->decode($raw); + } + + /** + * Implements Drupal\Core\Config\StorageInterface::listAll(). + */ + public function listAll($prefix = '') { + return $this->storages['file']->listAll($prefix); + } +} diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index ffddd61..2f4d14a 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -36,21 +36,21 @@ class Config { protected $data = array(); /** - * The injected storage dispatcher object. + * The storage used for reading and writing. * - * @var Drupal\Core\Config\StorageDispatcher + * @var Drupal\Core\Config\StorageInterface */ - protected $storageDispatcher; + protected $storage; /** * Constructs a configuration object. * - * @param Drupal\Core\Config\StorageDispatcher $storageDispatcher - * A storage dispatcher object to use for reading and writing the + * @param Drupal\Core\Config\StorageInterface $storage + * A storage controller object to use for reading and writing the * configuration data. */ - public function __construct(StorageDispatcher $storageDispatcher) { - $this->storageDispatcher = $storageDispatcher; + public function __construct(StorageInterface $storage) { + $this->storage = $storage; } /** @@ -224,7 +224,7 @@ public function clear($key) { * Loads configuration data into this object. */ public function load() { - $data = $this->storageDispatcher->selectStorage('read', $this->name)->read($this->name); + $data = $this->storage->read($this->name); if ($data === FALSE) { $this->isNew = TRUE; $this->setData(array()); @@ -241,7 +241,7 @@ public function load() { */ public function save() { $this->sortByKey($this->data); - $this->storageDispatcher->selectStorage('write', $this->name)->write($this->name, $this->data); + $this->storage->write($this->name, $this->data); $this->isNew = FALSE; return $this; } @@ -271,7 +271,7 @@ public function sortByKey(array &$data) { */ public function delete() { $this->data = array(); - $this->storageDispatcher->selectStorage('write', $this->name)->delete($this->name); + $this->storage->delete($this->name); $this->isNew = TRUE; return $this; } diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index 1fbca62..7c26b6e 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -15,29 +15,29 @@ * * @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. + * Each configuration object gets a storage controller object injected, which + * is used for reading and writing the configuration data. * - * @see Drupal\Core\Config\StorageDispatcher + * @see Drupal\Core\Config\StorageInterface */ class ConfigFactory { + /** - * A storage dispatcher instance to use for reading and writing configuration data. + * A storage controller instance for reading and writing configuration data. * - * @var Drupal\Core\Config\StorageDispatcher + * @var Drupal\Core\Config\StorageInterface */ - protected $storageDispatcher; + protected $storage; /** * Constructs the Config factory. * - * @param Drupal\Core\Config\StorageDispatcher $storage_dispatcher - * The storage dispatcher object to use for reading and writing + * @param Drupal\Core\Config\StorageInterface $storage + * The storage controller object to use for reading and writing * configuration data. */ - public function __construct(StorageDispatcher $storage_dispatcher) { - $this->storageDispatcher = $storage_dispatcher; + public function __construct(StorageInterface $storage) { + $this->storage = $storage; } /** @@ -68,7 +68,8 @@ public function get($name) { // @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); + $config = new Config($this->storage); return $config->setName($name); } + } diff --git a/core/lib/Drupal/Core/Config/StorageDispatcher.php b/core/lib/Drupal/Core/Config/StorageDispatcher.php deleted file mode 100644 index ebf7049..0000000 --- a/core/lib/Drupal/Core/Config/StorageDispatcher.php +++ /dev/null @@ -1,105 +0,0 @@ - array( - * 'target' => 'default', - * 'read' => TRUE, - * 'write' => TRUE, - * ), - * 'Drupal\Core\Config\FileStorage' => array( - * 'directory' => 'sites/default/files/config', - * 'read' => TRUE, - * 'write' => FALSE, - * ), - * ) - * @endcode - */ - public function __construct(array $storage_info) { - $this->storageInfo = $storage_info; - } - - /** - * Returns a storage controller to use for a given operation. - * - * Handles the core functionality of the storage dispatcher by determining - * which storage can handle a particular storage access operation and - * configuration object. - * - * @param string $access_operation - * The operation access level; either 'read' or 'write'. Use 'write' both - * for saving and deleting configuration. - * @param string $name - * The name of the configuration object that is operated on. - * - * @return Drupal\Core\Config\StorageInterface - * The storage controller instance that can handle the requested operation. - * - * @throws Drupal\Core\Config\ConfigException - * - * @todo Allow write operations to write to multiple storages. - */ - public function selectStorage($access_operation, $name) { - // Determine the appropriate storage controller to use. - // Take the first defined storage that allows $op. - foreach ($this->storageInfo as $class => $storage_config) { - if (!empty($storage_config[$access_operation])) { - $storage_class = $class; - break; - } - } - if (!isset($storage_class)) { - throw new ConfigException("Failed to find storage controller that allows $access_operation access for $name."); - } - - // Instantiate a new storage controller object, if there is none yet. - if (!isset($this->storageInstances[$storage_class])) { - $this->storageInstances[$storage_class] = new $storage_class($this->storageInfo[$storage_class]); - } - return $this->storageInstances[$storage_class]; - } -} diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php index a6e72b4..aee2bf0 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php @@ -69,22 +69,28 @@ function testNew() { $name = 'config_test.new'; $dynamic_name = 'config_test.dynamic.new'; + // Export. + config_export(); + // Verify the configuration to create does not exist yet. + $database_storage = new DatabaseStorage(); + $this->assertIdentical($database_storage->exists($name), FALSE, $name . ' not found.'); + $this->assertIdentical($database_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); + $file_storage = new FileStorage(); $this->assertIdentical($file_storage->exists($name), FALSE, $name . ' not found.'); $this->assertIdentical($file_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.'); - // Export. - config_export(); - // Create new configuration objects. - $file_storage->write($name, array( + $original_name_data = array( 'add_me' => 'new value', - )); - $file_storage->write($dynamic_name, array( + ); + $file_storage->write($name, $original_name_data); + $original_dynamic_data = array( 'id' => 'new', 'label' => 'New', - )); + ); + $file_storage->write($dynamic_name, $original_dynamic_data); $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.'); $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); @@ -93,9 +99,9 @@ function testNew() { // Verify the values appeared. $config = config($name); - $this->assertIdentical($config->get('add_me'), 'new value'); + $this->assertIdentical($config->get('add_me'), $original_name_data['add_me']); $config = config($dynamic_name); - $this->assertIdentical($config->get('label'), 'New'); + $this->assertIdentical($config->get('label'), $original_dynamic_data['label']); } /** @@ -108,17 +114,25 @@ function testUpdated() { // Export. config_export(); - // Replace the file content of the existing configuration objects. + // Verify that the configuration objects to import exist. + $database_storage = new DatabaseStorage(); + $this->assertIdentical($database_storage->exists($name), TRUE, $name . ' found.'); + $this->assertIdentical($database_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); + $file_storage = new FileStorage(); $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.'); $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.'); - $file_storage->write($name, array( + + // Replace the file content of the existing configuration objects. + $original_name_data = array( 'foo' => 'beer', - )); - $file_storage->write($dynamic_name, array( + ); + $file_storage->write($name, $original_name_data); + $original_dynamic_data = array( 'id' => 'default', 'label' => 'Updated', - )); + ); + $file_storage->write($dynamic_name, $original_dynamic_data); // Verify the active store still returns the default values. $config = config($name); @@ -134,5 +148,9 @@ function testUpdated() { $this->assertIdentical($config->get('foo'), 'beer'); $config = config($dynamic_name); $this->assertIdentical($config->get('label'), 'Updated'); + + // Verify that the original file content is still the same. + $this->assertIdentical($file_storage->read($name), $original_name_data); + $this->assertIdentical($file_storage->read($dynamic_name), $original_dynamic_data); } } diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php index b96049e..99fd422 100644 --- a/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php +++ b/core/modules/search/lib/Drupal/search/Tests/SearchExcerptTest.php @@ -7,12 +7,14 @@ namespace Drupal\search\Tests; -use Drupal\simpletest\UnitTestBase; +use Drupal\simpletest\WebTestBase; /** * Tests the search_excerpt() function. */ -class SearchExcerptTest extends UnitTestBase { +class SearchExcerptTest extends WebTestBase { + public static $modules = array('search'); + public static function getInfo() { return array( 'name' => 'Search excerpt extraction', @@ -21,11 +23,6 @@ public static function getInfo() { ); } - function setUp() { - drupal_load('module', 'search'); - parent::setUp(); - } - /** * Tests search_excerpt() with several simulated search keywords. *