diff --git a/core/core.services.yml b/core/core.services.yml index f3c6cd91d1c285ec1a18839304eee31aad7f85b5..baf6fe42a84437e43008f63d29fea727812700cd 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -74,16 +74,9 @@ services: factory_method: get factory_service: cache_factory arguments: [discovery] - 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: @@ -92,6 +85,15 @@ 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.file: + class: Drupal\Core\Config\FileStorage + factory_class: Drupal\Core\Config\FileStorageFactory + factory_method: getActive 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 f1cad478f80abba4eb187dbe28ba964075f809df..09c340aec8040e06a052f30dc6fb7be9aeee6285 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -438,7 +438,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 = \Drupal::service('config.storage')->listAll(); 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 e075261e04d3ecfd46e3ffbdd6380a16eb7d3796..ffc8f48f03fefe37deb66f47a106aff8242c1250 100644 --- a/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php +++ b/core/lib/Drupal/Core/Config/BootstrapConfigStorageFactory.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Config; +use Drupal\Core\Database\Database; use Drupal\Component\Utility\Settings; /** @@ -21,13 +22,29 @@ class BootstrapConfigStorageFactory { * A configuration storage implementation. */ 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)); + $bootstrap_config_storage = Settings::get('bootstrap_config_storage'); + if (!empty($bootstrap_config_storage) && is_callable($bootstrap_config_storage)) { + return call_user_func($bootstrap_config_storage); } + // Fallback to the DatabaseStorage. + return self::getDatabaseStorage(); + } + + /** + * Returns a Database configuration storage implementation. + * + * @return \Drupal\Core\Config\DatabaseStorage + */ + 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 7cbe2eba6ccfb1b1a18cc5d92fe9e71021bfacf7..76a5b333e8cc631758ad9e6c29343f462f26d4f9 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 e1e7faca8180050bb8ce4f76e8714da12753518e..11ab691230b2f0f74af41319aa85a189b361baed 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) @@ -121,6 +141,60 @@ public function write($name, array $data) { ->execute(); } + /** + * 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(). * @@ -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 fbb997051a83b21989e45b65c0659f86e7d6a5b0..0b0466b9135ffc1044600d3b266a001d6af44270 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -68,6 +68,18 @@ public static function getFileExtension() { return 'yml'; } + /** + * 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(). */ @@ -105,21 +117,23 @@ 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)); } @@ -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 8aa292bc7c8290ea2a1b2e3bb41f2bd7cd6ad3a6..18c0f07a11574520915fe03ad2ce6ad5dd568bb7 100644 --- a/core/lib/Drupal/Core/Config/StorableConfigBase.php +++ b/core/lib/Drupal/Core/Config/StorableConfigBase.php @@ -131,6 +131,27 @@ protected function getSchemaWrapper() { return $this->schemaWrapper; } + /** + * 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. * diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index 35cdf8aab9944789948b3e7da94ca41796385b94..8b997841ad2a2b6c71810ff7220cd710def796f2 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 aa3c709bf687159aa15e3c22aa9fe7cab8348ad1..4239d64522d425074fe0b7e138450311f12d6f8a 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/modules/config/lib/Drupal/config/Controller/ConfigController.php b/core/modules/config/lib/Drupal/config/Controller/ConfigController.php index 18f628704014d894e29cac685ce0370970a1a266..c79b8cb4bd1a53f22a92c2a92e55fe4a90fc45f1 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 101dacbd069e89518cb454086f5504bb60e746f1..cdb76cc72eadbc6bcc471047ba1402a12ac667d1 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. @@ -32,6 +33,13 @@ class ConfigSingleExportForm extends FormBase { */ protected $configStorage; + /** + * The YAML dumper. + * + * @var \Symfony\Component\Yaml\Dumper + */ + protected $dumper; + /** * Tracks the valid config entity type definitions. * @@ -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); $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 129586271e19ad9c279034e1d85166ed7ddf63ed..e20afdeb478b0f5bbcb67229e103d08a762d5913 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. @@ -31,6 +32,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. * @@ -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 7576163c395a4c98ec7f1708f823b0f333d7fdf4..f10a1202a94733f41e6f258f46552b6edcda7163 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('<pre>' . $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('<pre>' . file_get_contents($storage->getFilePath($name)) . var_export($storage->read($name), TRUE)); + $this->verbose('<pre>' . $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 46a14d472dc40faf4341a32a9155613b86722b9d..b131d8ade145c816db96b3b65824997735b9e182 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSingleImportExportTest.php @@ -8,7 +8,7 @@ namespace Drupal\config\Tests; use Drupal\simpletest\WebTestBase; -use Symfony\Component\Yaml\Yaml; +use Symfony\Component\Yaml\Dumper; /** * Tests the user interface for importing/exporting a single configuration. @@ -22,6 +22,13 @@ class ConfigSingleImportExportTest extends WebTestBase { */ public static $modules = array('config', 'config_test'); + /** + * The YAML dumper. + * + * @var \Symfony\Component\Yaml\Dumper + */ + protected $dumper; + public static function getInfo() { return array( 'name' => 'Configuration Single Import/Export UI', @@ -30,6 +37,15 @@ public static function getInfo() { ); } + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + $this->dumper = new Dumper(); + $this->dumper->setIndentation(2); + } + /** * Tests importing a single configuration file. */ @@ -111,12 +127,11 @@ public function testImport() { */ public function testImportSimpleConfiguration() { $this->drupalLogin($this->drupalCreateUser(array('import configuration'))); - $yaml = new Yaml(); $config = \Drupal::config('system.site')->set('name', 'Test simple import'); $edit = array( 'config_type' => 'system.simple', 'config_name' => $config->getName(), - 'import' => $yaml->dump($config->get()), + 'import' => $this->dumper->dump($config->get(), PHP_INT_MAX), ); $this->drupalPostForm('admin/config/development/configuration/single/import', $edit, t('Import')); $this->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => $config->getName(), '@type' => 'simple configuration'))); @@ -152,7 +167,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 = $this->dumper->dump($fallback_date->toArray(), PHP_INT_MAX); $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 140b50a009c20f59d913dacdd484d8fa65479591..cef916b25503d13a09f53e236b3bc85747cdc5e7 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 66cdb995d7d76c380c5ec1d3ca2594103cf11520..7dbf17bb461ac7bc156b715e13a67358c0190af6 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 dd6c4009d12254982eb92978967cbc121ca9665e..96bdb428f4b0400b26d1ab6b312c150081e21985 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 405aaf568b9dcd729cf416db689565b69325a909..cb07cd4227b8f21909ae7a5158da156016624e29 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/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php index 75e3f781a0028f817bbb18d6cdb9af0af897ced7..3d35566419acf5af840e7b6366e9c8fd307a7c6c 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php @@ -229,8 +229,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 3eac57467a4712b7b19f04f842b666eeb6908465..b2ce930eff03beac67c0642f521c6adc064c4178 100644 --- a/sites/default/default.settings.php +++ b/sites/default/default.settings.php @@ -600,6 +600,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', 'getFileStorage'); + /** * Configuration overrides. *