diff --git a/core/core.services.yml b/core/core.services.yml index 38acedf..1ad1f1e 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -67,16 +67,9 @@ services: factory_method: get factory_service: cache_factory arguments: [data] - config.cachedstorage.storage: - class: Drupal\Core\Config\FileStorage - factory_class: Drupal\Core\Config\FileStorageFactory - factory_method: getActive config.manager: class: Drupal\Core\Config\ConfigManager arguments: ['@entity.manager', '@config.factory', '@config.typed', '@string_translation', '@config.storage'] - config.storage: - class: Drupal\Core\Config\CachedStorage - arguments: ['@config.cachedstorage.storage', '@cache.config'] config.factory: class: Drupal\Core\Config\ConfigFactory tags: @@ -85,6 +78,11 @@ services: config.installer: class: Drupal\Core\Config\ConfigInstaller arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher'] + config.storage: + alias: config.storage.active + config.storage.active: + class: Drupal\Core\Config\DatabaseStorage + arguments: ['@database', 'config'] config.storage.staging: class: Drupal\Core\Config\FileStorage factory_class: Drupal\Core\Config\FileStorageFactory diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index aa158a2..35dd4c9 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -439,7 +439,7 @@ function install_begin_request(&$install_state) { // Ensure that the active configuration directory is empty before installation // starts. if ($install_state['config_verified'] && empty($task)) { - $config = glob(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY) . '/*.' . FileStorage::getFileExtension()); + $config = Database::getConnection()->schema()->tableExists('config'); if (!empty($config)) { $task = NULL; throw new AlreadyInstalledException($container->get('string_translation')); diff --git a/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php b/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php index e075261..57bea76 100644 --- a/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php +++ b/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php @@ -7,7 +7,7 @@ namespace Drupal\Core\Config; -use Drupal\Component\Utility\Settings; +use Drupal\Core\Database\Database; /** * Defines a factory for retrieving the config storage used pre-kernel. @@ -15,19 +15,20 @@ class BootstrapConfigStorageFactory { /** - * Returns a configuration storage implementation. + * Returns a Database configuration storage implementation. * - * @return \Drupal\Core\Config\StorageInterface - * A configuration storage implementation. + * @return \Drupal\Core\Config\DatabaseStorage */ - public static function get() { - $drupal_bootstrap_config_storage = Settings::get('drupal_bootstrap_config_storage'); - if ($drupal_bootstrap_config_storage && is_callable($drupal_bootstrap_config_storage)) { - return call_user_func($drupal_bootstrap_config_storage); - } - else { - return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY)); - } + public static function getDatabaseStorage() { + return new DatabaseStorage(Database::getConnection(), 'config'); } + /** + * Returns a File-based configuration storage implementation. + * + * @return \Drupal\Core\Config\FileStorage + */ + public static function getFileStorage() { + return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY)); + } } diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 19899a5..0ba01b4 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -2,7 +2,7 @@ /** * @file - * Definition of Drupal\Core\Config\Config. + * Contains \Drupal\Core\Config\Config. */ namespace Drupal\Core\Config; @@ -215,6 +215,11 @@ public function save() { $this->data[$key] = $this->castValue($key, $value); } } + else { + foreach ($this->data as $key => $value) { + $this->validateValue($key, $value); + } + } $this->storage->write($this->name, $this->data); $this->isNew = FALSE; diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index fd8f2ee..ed0bd72 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -2,13 +2,14 @@ /** * @file - * Definition of Drupal\Core\Config\DatabaseStorage. + * Contains \Drupal\Core\Config\DatabaseStorage. */ namespace Drupal\Core\Config; use Drupal\Core\Database\Database; use Drupal\Core\Database\Connection; +use Drupal\Core\Database\SchemaObjectExistsException; /** * Defines the Database storage. @@ -56,24 +57,23 @@ public function __construct(Connection $connection, $table, array $options = arr * Implements Drupal\Core\Config\StorageInterface::exists(). */ public function exists($name) { - return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', 0, 1, array( - ':name' => $name, - ), $this->options)->fetchField(); + try { + return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', 0, 1, array( + ':name' => $name, + ), $this->options)->fetchField(); + } + catch (\Exception $e) { + // If we attempt a read without actually having the database or the table + // available, just return FALSE so the caller can handle it. + return FALSE; + } } /** - * Implements Drupal\Core\Config\StorageInterface::read(). - * - * @throws PDOException - * @throws \Drupal\Core\Database\DatabaseExceptionWrapper - * Only thrown in case $this->options['throw_exception'] is TRUE. + * {@inheritdoc} */ public function read($name) { $data = FALSE; - // 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. try { $raw = $this->connection->query('SELECT data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', array(':name' => $name), $this->options)->fetchField(); if ($raw !== FALSE) { @@ -81,6 +81,8 @@ public function read($name) { } } catch (\Exception $e) { + // If we attempt a read without actually having the database or the table + // available, just return FALSE so the caller can handle it. } return $data; } @@ -89,10 +91,6 @@ public function read($name) { * {@inheritdoc} */ public function readMultiple(array $names) { - // 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. $list = array(); try { $list = $this->connection->query('SELECT name, data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name IN (:names)', array(':names' => $names), $this->options)->fetchAllKeyed(); @@ -100,20 +98,42 @@ public function readMultiple(array $names) { $data = $this->decode($data); } } - catch (Exception $e) { + catch (\Exception $e) { + // If we attempt a read without actually having the database or the table + // available, just return an empty array so the caller can handle it. } return $list; } /** - * Implements Drupal\Core\Config\StorageInterface::write(). - * - * @throws PDOException - * - * @todo Ignore slave targets for data manipulation operations. + * {@inheritdoc} */ public function write($name, array $data) { $data = $this->encode($data); + try { + return $this->doWrite($name, $data); + } + catch (\Exception $e) { + // If there was an exception, try to create the table. + if ($this->ensureTableExists()) { + return $this->doWrite($name, $data); + } + // Some other failure that we can not recover from. + throw $e; + } + } + + /** + * Helper method so we can re-try a write. + * + * @param string $name + * The config name. + * @param string $data + * The config data, already dumped to a string. + * + * @return bool + */ + protected function doWrite($name, $data) { $options = array('return' => Database::RETURN_AFFECTED) + $this->options; return (bool) $this->connection->merge($this->table, $options) ->key('name', $name) @@ -122,6 +142,60 @@ public function write($name, array $data) { } /** + * Check if the config table exists and create it if not. + * + * @return bool + * TRUE if the table was created, FALSE otherwise. + * + * @throws \Drupal\Core\Config\StorageException + * If a database error occurs. + */ + protected function ensureTableExists() { + try { + if (!$this->connection->schema()->tableExists($this->table)) { + $this->connection->schema()->createTable($this->table, static::schemaDefinition()); + return TRUE; + } + } + // If another process has already created the config table, attempting to + // recreate it will throw an exception. In this case just catch the + // exception and do nothing. + catch (SchemaObjectExistsException $e) { + return TRUE; + } + catch (\Exception $e) { + throw new StorageException($e->getMessage(), NULL, $e); + } + return FALSE; + } + + /** + * Defines the schema for the configuration table. + */ + protected static function schemaDefinition() { + $schema = array( + 'description' => 'The base table for configuration data.', + 'fields' => array( + 'name' => array( + 'description' => 'Primary Key: Unique config object name.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'data' => array( + 'description' => 'A serialized configuration object data.', + 'type' => 'blob', + 'not null' => FALSE, + 'size' => 'big', + ), + ), + 'primary key' => array('name'), + ); + return $schema; + } + + /** * Implements Drupal\Core\Config\StorageInterface::delete(). * * @throws PDOException @@ -168,29 +242,31 @@ public function decode($raw) { } /** - * Implements Drupal\Core\Config\StorageInterface::listAll(). - * - * @throws PDOException - * @throws \Drupal\Core\Database\DatabaseExceptionWrapper - * Only thrown in case $this->options['throw_exception'] is TRUE. + * {@inheritdoc} */ public function listAll($prefix = '') { - return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name LIKE :name', array( - ':name' => db_like($prefix) . '%', - ), $this->options)->fetchCol(); + try { + return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name LIKE :name', array( + ':name' => $this->connection->escapeLike($prefix) . '%', + ), $this->options)->fetchCol(); + } + catch (\Exception $e) { + return array(); + } } /** - * Implements Drupal\Core\Config\StorageInterface::deleteAll(). - * - * @throws PDOException - * @throws \Drupal\Core\Database\DatabaseExceptionWrapper - * Only thrown in case $this->options['throw_exception'] is TRUE. + * {@inheritdoc} */ public function deleteAll($prefix = '') { - $options = array('return' => Database::RETURN_AFFECTED) + $this->options; - return (bool) $this->connection->delete($this->table, $options) - ->condition('name', $prefix . '%', 'LIKE') - ->execute(); + try { + $options = array('return' => Database::RETURN_AFFECTED) + $this->options; + return (bool) $this->connection->delete($this->table, $options) + ->condition('name', $prefix . '%', 'LIKE') + ->execute(); + } + catch (\Exception $e) { + return FALSE; + } } } diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index 14268e9..d660169 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -69,6 +69,18 @@ public static function getFileExtension() { } /** + * Check if the directory exists and create it if not. + */ + protected function ensureStorage() { + $success = file_prepare_directory($this->directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + $success = $success && file_save_htaccess($this->directory, TRUE, TRUE); + if (!$success) { + throw new StorageException("Failed to create config directory {$this->directory}"); + } + return $this; + } + + /** * Implements Drupal\Core\Config\StorageInterface::exists(). */ public function exists($name) { @@ -105,22 +117,24 @@ public function readMultiple(array $names) { } /** - * Implements Drupal\Core\Config\StorageInterface::write(). - * - * @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException - * @throws \Drupal\Core\Config\StorageException + * {@inheritdoc} */ public function write($name, array $data) { try { $data = $this->encode($data); } catch(DumpException $e) { - throw new UnsupportedDataTypeConfigException(String::format('Invalid data type for used in config: @name', array('@name' => $name))); + throw new StorageException(String::format('Invalid data type for used in config: @name', array('@name' => $name))); } $target = $this->getFilePath($name); $status = @file_put_contents($target, $data); if ($status === FALSE) { + // Try to make sure the directory exists and try witing again. + $this->ensureStorage(); + $status = @file_put_contents($target, $data); + } + if ($status === FALSE) { throw new StorageException('Failed to write configuration file: ' . $this->getFilePath($name)); } else { @@ -213,7 +227,7 @@ public function listAll($prefix = '') { // glob() silently ignores the error of a non-existing search directory, // even with the GLOB_ERR flag. if (!file_exists($this->directory)) { - throw new StorageException($this->directory . '/ not found.'); + return array(); } $extension = '.' . static::getFileExtension(); // \GlobIterator on Windows requires an absolute path. diff --git a/core/lib/Drupal/Core/Config/StorableConfigBase.php b/core/lib/Drupal/Core/Config/StorableConfigBase.php index 8aa292b..18c0f07 100644 --- a/core/lib/Drupal/Core/Config/StorableConfigBase.php +++ b/core/lib/Drupal/Core/Config/StorableConfigBase.php @@ -132,6 +132,27 @@ protected function getSchemaWrapper() { } /** + * Validate the values are allowed data types. + * + * @throws UnsupportedDataTypeConfigException + * If there is any invalid value. + */ + protected function validateValue($key, $value) { + // Minimal validation. Should not try to serialize resources or non-arrays. + if (is_array($value)) { + foreach ($value as $nested_value_key => $nested_value) { + $this->validateValue($key . '.' . $nested_value_key, $nested_value); + } + } + elseif ($value !== NULL && !is_scalar($value)) { + throw new UnsupportedDataTypeConfigException(String::format('Invalid data type for config element @name:@key', array( + '@name' => $this->getName(), + '@key' => $key, + ))); + } + } + + /** * Casts the value to correct data type using the configuration schema. * * @param string $key diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index 9b45fa8..4e23761 100644 --- a/core/lib/Drupal/Core/Config/StorageInterface.php +++ b/core/lib/Drupal/Core/Config/StorageInterface.php @@ -41,7 +41,7 @@ public function read($name); /** * Reads configuration data from the storage. * - * @param array $name + * @param array $names * List of names of the configuration objects to load. * * @return array @@ -60,6 +60,9 @@ public function readMultiple(array $names); * * @return bool * TRUE on success, FALSE in case of an error. + * + * @throws \Drupal\Core\Config\StorageException + * If the back-end storage does not exist and cannot be created. */ public function write($name, array $data); diff --git a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php index aa3c709..4239d64 100644 --- a/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php +++ b/core/lib/Drupal/Core/DependencyInjection/UpdateServiceProvider.php @@ -28,11 +28,11 @@ public function register(ContainerBuilder $container) { $container ->register('lock', 'Drupal\Core\Lock\NullLockBackend'); - // Prevent config from accessing {cache_config}. - // @see $conf['cache_classes'], update_prepare_d8_bootstrap() - $container - ->register('config.storage', 'Drupal\Core\Config\FileStorage') - ->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY)); + // Prevent config from being accessed via a cache wrapper by removing + // any existing definition and setting an alias to the actual storage. + $container->removeDefinition('config.storage'); + $container->setAlias('config.storage', 'config.storage.active'); + $container->register('module_handler', 'Drupal\Core\Extension\UpdateModuleHandler') ->addArgument('%container.modules%'); $container diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index 8812cab..0482bc6 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -7,10 +7,9 @@ namespace Drupal\Core; +use Drupal\Component\Utility\Settings; use Drupal\Core\PhpStorage\PhpStorageFactory; -use Drupal\Core\Config\BootstrapConfigStorageFactory; use Drupal\Core\Config\NullStorage; -use Drupal\Core\CoreServiceProvider; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderInterface; use Drupal\Core\DependencyInjection\YamlFileLoader; @@ -649,13 +648,17 @@ protected function storage() { */ protected function getConfigStorage() { if (!isset($this->configStorage)) { + $this->configStorage = new NullStorage(); // The active configuration storage may not exist yet; e.g., in the early // installer. Catch the exception thrown by config_get_config_directory(). try { - $this->configStorage = BootstrapConfigStorageFactory::get(); + $bootstrap_config_storage = Settings::get('bootstrap_config_storage'); + if (is_callable($bootstrap_config_storage)) { + $this->configStorage = call_user_func($bootstrap_config_storage); + } } catch (\Exception $e) { - $this->configStorage = new NullStorage(); + // Use the NullStorage. } } return $this->configStorage; diff --git a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php index 18f6287..c79b8cb 100644 --- a/core/modules/config/lib/Drupal/config/Controller/ConfigController.php +++ b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php @@ -12,6 +12,7 @@ use Drupal\Core\Config\StorageInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\system\FileDownloadController; +use Symfony\Component\Yaml\Dumper; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; @@ -81,13 +82,15 @@ public function __construct(StorageInterface $target_storage, StorageInterface $ * Downloads a tarball of the site configuration. */ public function downloadExport() { + file_unmanaged_delete(file_directory_temp() . '/config.tar.gz'); + + $dumper = new Dumper(); + $dumper->setIndentation(2); + $archiver = new ArchiveTar(file_directory_temp() . '/config.tar.gz', 'gz'); - $config_dir = config_get_config_directory(); - $config_files = array(); - foreach (\Drupal::service('config.storage')->listAll() as $config_name) { - $config_files[] = $config_dir . '/' . $config_name . '.yml'; + foreach (\Drupal::service('config.storage')->listAll() as $name) { + $archiver->addString("$name.yml", $dumper->dump(\Drupal::config($name)->get(), PHP_INT_MAX, 0, TRUE)); } - $archiver->createModify($config_files, '', config_get_config_directory()); $request = new Request(array('file' => 'config.tar.gz')); return $this->fileDownloadController->download($request, 'temporary'); diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php index 101dacb..4b2b6f7 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSingleExportForm.php @@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Form\FormBase; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Yaml\Dumper; /** * Provides a form for exporting a single configuration file. @@ -33,6 +34,13 @@ class ConfigSingleExportForm extends FormBase { protected $configStorage; /** + * The YAML dumper. + * + * @var \Symfony\Component\Yaml\Dumper + */ + protected $dumper; + + /** * Tracks the valid config entity type definitions. * * @var \Drupal\Core\Entity\EntityTypeInterface[] @@ -46,10 +54,14 @@ class ConfigSingleExportForm extends FormBase { * The entity manager. * @param \Drupal\Core\Config\StorageInterface $config_storage * The config storage. + * @param \Symfony\Component\Yaml\Dumper $dumper + * The yaml dumper. */ - public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage) { + public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, Dumper $dumper) { $this->entityManager = $entity_manager; $this->configStorage = $config_storage; + $this->dumper = $dumper; + $this->dumper->setIndentation(2); } /** @@ -58,7 +70,8 @@ public function __construct(EntityManagerInterface $entity_manager, StorageInter public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), - $container->get('config.storage') + $container->get('config.storage'), + new Dumper() ); } @@ -151,8 +164,7 @@ public function updateExport($form, &$form_state) { $name = $form_state['values']['config_name']; } // Read the raw data for this config name, encode it, and display it. - $data = $this->configStorage->read($name); - $form['export']['#value'] = $this->configStorage->encode($data); + $form['export']['#value'] = $this->dumper->dump($this->configStorage->read($name), PHP_INT_MAX, 0, TRUE); $form['export']['#description'] = $this->t('The filename is %name.', array('%name' => $name . '.yml')); return $form['export']; } diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php index 1295862..e20afde 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php @@ -11,6 +11,7 @@ use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Form\ConfirmFormBase; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Yaml\Yaml; /** * Provides a form for importing a single configuration file. @@ -32,6 +33,13 @@ class ConfigSingleImportForm extends ConfirmFormBase { protected $configStorage; /** + * The YAML component. + * + * @var \Symfony\Component\Yaml\Yaml + */ + protected $yaml; + + /** * If the config exists, this is that object. Otherwise, FALSE. * * @var \Drupal\Core\Config\Config|\Drupal\Core\Config\Entity\ConfigEntityInterface|bool @@ -52,10 +60,13 @@ class ConfigSingleImportForm extends ConfirmFormBase { * The entity manager. * @param \Drupal\Core\Config\StorageInterface $config_storage * The config storage. + * @param \Symfony\Component\Yaml\Yaml $yaml + * The YAML component. */ - public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage) { + public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, Yaml $yaml) { $this->entityManager = $entity_manager; $this->configStorage = $config_storage; + $this->yaml = $yaml; } /** @@ -64,7 +75,8 @@ public function __construct(EntityManagerInterface $entity_manager, StorageInter public static function create(ContainerInterface $container) { return new static( $container->get('entity.manager'), - $container->get('config.storage') + $container->get('config.storage'), + new Yaml() ); } @@ -184,7 +196,7 @@ public function validateForm(array &$form, array &$form_state) { } // Decode the submitted import. - $data = $this->configStorage->decode($form_state['values']['import']); + $data = $this->yaml->parse($form_state['values']['import']); // Validate for config entities. if ($form_state['values']['config_type'] !== 'system.simple') { diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php index 7576163..f10a120 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigCRUDTest.php @@ -11,6 +11,7 @@ use Drupal\Core\Config\ConfigNameException; use Drupal\simpletest\DrupalUnitTestBase; use Drupal\Core\Config\FileStorage; +use Drupal\Core\Config\DatabaseStorage; use Drupal\Core\Config\UnsupportedDataTypeConfigException; /** @@ -192,10 +193,10 @@ function testNameValidation() { */ public function testDataTypes() { \Drupal::moduleHandler()->install(array('config_test')); - $storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); + $storage = new DatabaseStorage($this->container->get('database'), 'config'); $name = 'config_test.types'; $config = $this->container->get('config.factory')->get($name); - $original_content = file_get_contents($storage->getFilePath($name)); + $original_content = file_get_contents(drupal_get_path('module', 'config_test') . "/config/$name.yml"); $this->verbose('
' . $original_content . "\n" . var_export($storage->read($name), TRUE));
 
     // Verify variable data types are intact.
@@ -220,7 +221,7 @@ public function testDataTypes() {
     $this->assertIdentical($config->get(), $data);
     // Assert the data against the file storage.
     $this->assertIdentical($storage->read($name), $data);
-    $this->verbose('
' . file_get_contents($storage->getFilePath($name)) . var_export($storage->read($name), TRUE));
+    $this->verbose('
' . $name . var_export($storage->read($name), TRUE));
 
     // Set data using config::setData().
     $config->setData($data)->save();
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php
index 46a14d4..5f1a416 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php
@@ -130,6 +130,7 @@ public function testImportSimpleConfiguration() {
    */
   public function testExport() {
     $this->drupalLogin($this->drupalCreateUser(array('export configuration')));
+    $yaml = new Yaml();
 
     $this->drupalGet('admin/config/development/configuration/single/export/system.simple');
     $this->assertFieldByXPath('//select[@name="config_type"]//option[@selected="selected"]', t('Simple configuration'), 'The simple configuration option is selected when specified in the URL.');
@@ -152,7 +153,7 @@ public function testExport() {
     $this->assertFieldByXPath('//select[@name="config_name"]//option[@selected="selected"]', t('Fallback date format'), 'The fallback date format config entity is selected when specified in the URL.');
 
     $fallback_date = \Drupal::entityManager()->getStorage('date_format')->load('fallback');
-    $data = \Drupal::service('config.storage')->encode($fallback_date->toArray());
+    $data = $yaml->dump($fallback_date->toArray(), 2, 2);
     $this->assertFieldByXPath('//textarea[@name="export"]', $data, 'The fallback date format config entity export code is displayed.');
   }
 
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
index 140b50a..cef916b 100644
--- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
@@ -95,6 +95,10 @@ function testCRUD() {
     $result = $this->invalidStorage->read($name);
     $this->assertIdentical($result, FALSE);
 
+    // Listing on a non-existing storage bin returns an empty array.
+    $result = $this->invalidStorage->listAll();
+    $this->assertIdentical($result, array());
+
     // Deleting all names with prefix deletes the appropriate data and returns
     // TRUE.
     $files = array(
@@ -111,15 +115,6 @@ function testCRUD() {
     $this->assertIdentical($result, TRUE);
     $this->assertIdentical($names, array());
 
-    // Writing to a non-existing storage bin throws an exception.
-    try {
-      $this->invalidStorage->write($name, array('foo' => 'bar'));
-      $this->fail('Exception not thrown upon writing to a non-existing storage bin.');
-    }
-    catch (\Exception $e) {
-      $class = get_class($e);
-      $this->pass($class . ' thrown upon writing to a non-existing storage bin.');
-    }
 
     // Deleting from a non-existing storage bin throws an exception.
     try {
@@ -131,16 +126,6 @@ function testCRUD() {
       $this->pass($class . ' thrown upon deleting from a non-existing storage bin.');
     }
 
-    // Listing on a non-existing storage bin throws an exception.
-    try {
-      $this->invalidStorage->listAll();
-      $this->fail('Exception not thrown upon listing from a non-existing storage bin.');
-    }
-    catch (\Exception $e) {
-      $class = get_class($e);
-      $this->pass($class . ' thrown upon listing from a non-existing storage bin.');
-    }
-
     // Test renaming an object that does not exist throws an exception.
     try {
       $this->storage->rename('config_test.storage_does_not_exist', 'config_test.storage_does_not_exist_rename');
@@ -159,6 +144,10 @@ function testCRUD() {
       $this->pass($class . ' thrown upon renaming a nonexistent storage bin.');
     }
 
+    // Writing to a non-existing storage bin creates the bin.
+    $this->invalidStorage->write($name, array('foo' => 'bar'));
+    $result = $this->invalidStorage->read($name);
+    $this->assertIdentical($result, array('foo' => 'bar'));
   }
 
   /**
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php
index 516bb9d..eba9873 100644
--- a/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/DatabaseStorageTest.php
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Definition of Drupal\config\Tests\Storage\DatabaseStorageTest.
+ * Contains \Drupal\config\Tests\Storage\DatabaseStorageTest.
  */
 
 namespace Drupal\config\Tests\Storage;
@@ -24,28 +24,6 @@ public static function getInfo() {
   function setUp() {
     parent::setUp();
 
-    $schema['config'] = array(
-      'description' => 'Database storage for the configuration system.',
-      'fields' => array(
-        'name' => array(
-          'description' => 'The identifier for the configuration entry, such as module.example (the name of the file, minus the file extension).',
-          'type' => 'varchar',
-          'length' => 255,
-          'not null' => TRUE,
-          'default' => '',
-        ),
-        'data' => array(
-          'description' => 'The raw data for this configuration entry.',
-          'type' => 'blob',
-          'not null' => TRUE,
-          'size' => 'big',
-          'translatable' => TRUE,
-        ),
-      ),
-      'primary key' => array('name'),
-    );
-    db_create_table('config', $schema['config']);
-
     $this->storage = new DatabaseStorage($this->container->get('database'), 'config');
     $this->invalidStorage = new DatabaseStorage($this->container->get('database'), 'invalid');
 
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php b/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php
index d430bd3..bcd49989 100644
--- a/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/FileStorageTest.php
@@ -29,6 +29,7 @@ function setUp() {
 
     // FileStorage::listAll() requires other configuration data to exist.
     $this->storage->write('system.performance', \Drupal::config('system.performance')->get());
+    $this->storage->write('core.extension', array('module' => array()));
   }
 
   protected function read($name) {
diff --git a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
index 405aaf5..cb07cd4 100644
--- a/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/lib/Drupal/config_translation/Tests/ConfigTranslationUiTest.php
@@ -368,7 +368,6 @@ public function testContactConfigEntityTranslation() {
    */
   public function testDateFormatTranslation() {
     $this->drupalLogin($this->admin_user);
-    $file_storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
 
     $this->drupalGet('admin/config/regional/date-time');
 
diff --git a/core/modules/language/lib/Drupal/language/LanguageServiceProvider.php b/core/modules/language/lib/Drupal/language/LanguageServiceProvider.php
index 95eb700..3334389 100644
--- a/core/modules/language/lib/Drupal/language/LanguageServiceProvider.php
+++ b/core/modules/language/lib/Drupal/language/LanguageServiceProvider.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\language;
 
-use Drupal\Core\Config\BootstrapConfigStorageFactory;
+use Drupal\Component\Utility\Settings;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\ServiceProviderBase;
 use Drupal\Core\Language\Language;
@@ -79,11 +79,15 @@ protected function isMultilingual() {
     // @todo Try to swap out for config.storage to take advantage of database
     //   and caching. This might prove difficult as this is called before the
     //   container has finished building.
-    $config_storage = BootstrapConfigStorageFactory::get();
-    $config_ids = array_filter($config_storage->listAll($prefix), function($config_id) use ($prefix) {
-      return $config_id != $prefix . Language::LANGCODE_NOT_SPECIFIED && $config_id != $prefix . Language::LANGCODE_NOT_APPLICABLE;
-    });
-    return count($config_ids) > 1;
+    $bootstrap_config_storage = Settings::get('bootstrap_config_storage');
+    if (is_callable($bootstrap_config_storage)) {
+      $config_storage = call_user_func($bootstrap_config_storage);
+      $config_ids = array_filter($config_storage->listAll($prefix), function($config_id) use ($prefix) {
+        return $config_id != $prefix . Language::LANGCODE_NOT_SPECIFIED && $config_id != $prefix . Language::LANGCODE_NOT_APPLICABLE;
+      });
+      return count($config_ids) > 1;
+    }
+    return FALSE;
   }
 
   /**
@@ -95,11 +99,14 @@ protected function isMultilingual() {
    *   otherwise FALSE.
    */
   protected function getDefaultLanguageValues() {
-    $config_storage = BootstrapConfigStorageFactory::get();
-    $system = $config_storage->read('system.site');
-    $default_language = $config_storage->read(static::CONFIG_PREFIX . $system['langcode']);
-    if (is_array($default_language)) {
-      return $default_language + array('default' => TRUE);
+    $bootstrap_config_storage = Settings::get('bootstrap_config_storage');
+    if (is_callable($bootstrap_config_storage)) {
+      $config_storage = call_user_func($bootstrap_config_storage);
+      $system = $config_storage->read('system.site');
+      $default_language = $config_storage->read(static::CONFIG_PREFIX . $system['langcode']);
+      if (is_array($default_language)) {
+        return $default_language + array('default' => TRUE);
+      }
     }
     return FALSE;
   }
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
index 36adb8b..5379a5b 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php
@@ -225,8 +225,9 @@ public function containerBuild(ContainerBuilder $container) {
     $container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
 
     $container
-      ->register('config.storage', 'Drupal\Core\Config\FileStorage')
-      ->addArgument($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
+      ->register('config.storage.active', 'Drupal\Core\Config\DatabaseStorage')
+      ->addArgument(Database::getConnection())
+      ->addArgument('config');
 
     $this->settingsSet('keyvalue_default', 'keyvalue.memory');
     $container->set('keyvalue.memory', $this->keyValueFactory);
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 3eac574..d9003ed 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -601,6 +601,19 @@
 # $cookie_domain = '.example.com';
 
 /**
+ * Active configuration settings.
+ *
+ * By default, the active configuration is stored in the database in the
+ * {config} table. To install Drupal with a different active configuration
+ * storage, you need to override the setting here, in addition to overriding
+ * the config.storage.active service definition in a module or profile.
+ *
+ * The 'bootstrap_config_storage' setting needs to be a callable that returns
+ * core.services.yml.
+ */
+ $settings['bootstrap_config_storage'] = array('Drupal\Core\Config\BootstrapConfigStorageFactory', 'getDatabaseStorage');
+
+/**
  * Configuration overrides.
  *
  * To globally override specific configuration values for this site,