Issue #2161591 by pwolanin, beejeebus, sun: Change default active config from file storage to DB storage.

8.0.x
Dries 2014-04-14 22:23:05 +02:00
parent 23b38b7294
commit abc7e15fa6
20 changed files with 293 additions and 131 deletions

View File

@ -74,16 +74,9 @@ services:
factory_method: get factory_method: get
factory_service: cache_factory factory_service: cache_factory
arguments: [discovery] arguments: [discovery]
config.cachedstorage.storage:
class: Drupal\Core\Config\FileStorage
factory_class: Drupal\Core\Config\FileStorageFactory
factory_method: getActive
config.manager: config.manager:
class: Drupal\Core\Config\ConfigManager class: Drupal\Core\Config\ConfigManager
arguments: ['@entity.manager', '@config.factory', '@config.typed', '@string_translation', '@config.storage'] 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: config.factory:
class: Drupal\Core\Config\ConfigFactory class: Drupal\Core\Config\ConfigFactory
tags: tags:
@ -92,6 +85,15 @@ services:
config.installer: config.installer:
class: Drupal\Core\Config\ConfigInstaller class: Drupal\Core\Config\ConfigInstaller
arguments: ['@config.factory', '@config.storage', '@config.typed', '@config.manager', '@event_dispatcher'] 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: config.storage.staging:
class: Drupal\Core\Config\FileStorage class: Drupal\Core\Config\FileStorage
factory_class: Drupal\Core\Config\FileStorageFactory factory_class: Drupal\Core\Config\FileStorageFactory

View File

@ -438,7 +438,7 @@ function install_begin_request(&$install_state) {
// Ensure that the active configuration directory is empty before installation // Ensure that the active configuration directory is empty before installation
// starts. // starts.
if ($install_state['config_verified'] && empty($task)) { 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)) { if (!empty($config)) {
$task = NULL; $task = NULL;
throw new AlreadyInstalledException($container->get('string_translation')); throw new AlreadyInstalledException($container->get('string_translation'));

View File

@ -7,6 +7,7 @@
namespace Drupal\Core\Config; namespace Drupal\Core\Config;
use Drupal\Core\Database\Database;
use Drupal\Component\Utility\Settings; use Drupal\Component\Utility\Settings;
/** /**
@ -21,13 +22,29 @@ class BootstrapConfigStorageFactory {
* A configuration storage implementation. * A configuration storage implementation.
*/ */
public static function get() { public static function get() {
$drupal_bootstrap_config_storage = Settings::get('drupal_bootstrap_config_storage'); $bootstrap_config_storage = Settings::get('bootstrap_config_storage');
if ($drupal_bootstrap_config_storage && is_callable($drupal_bootstrap_config_storage)) { if (!empty($bootstrap_config_storage) && is_callable($bootstrap_config_storage)) {
return call_user_func($drupal_bootstrap_config_storage); return call_user_func($bootstrap_config_storage);
}
else {
return new FileStorage(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
} }
// 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));
}
} }

View File

@ -2,7 +2,7 @@
/** /**
* @file * @file
* Definition of Drupal\Core\Config\Config. * Contains \Drupal\Core\Config\Config.
*/ */
namespace Drupal\Core\Config; namespace Drupal\Core\Config;
@ -215,6 +215,11 @@ class Config extends StorableConfigBase {
$this->data[$key] = $this->castValue($key, $value); $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->storage->write($this->name, $this->data);
$this->isNew = FALSE; $this->isNew = FALSE;

View File

@ -2,13 +2,14 @@
/** /**
* @file * @file
* Definition of Drupal\Core\Config\DatabaseStorage. * Contains \Drupal\Core\Config\DatabaseStorage.
*/ */
namespace Drupal\Core\Config; namespace Drupal\Core\Config;
use Drupal\Core\Database\Database; use Drupal\Core\Database\Database;
use Drupal\Core\Database\Connection; use Drupal\Core\Database\Connection;
use Drupal\Core\Database\SchemaObjectExistsException;
/** /**
* Defines the Database storage. * Defines the Database storage.
@ -56,24 +57,23 @@ class DatabaseStorage implements StorageInterface {
* Implements Drupal\Core\Config\StorageInterface::exists(). * Implements Drupal\Core\Config\StorageInterface::exists().
*/ */
public function exists($name) { public function exists($name) {
return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', 0, 1, array( try {
':name' => $name, return (bool) $this->connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', 0, 1, array(
), $this->options)->fetchField(); ':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(). * {@inheritdoc}
*
* @throws PDOException
* @throws \Drupal\Core\Database\DatabaseExceptionWrapper
* Only thrown in case $this->options['throw_exception'] is TRUE.
*/ */
public function read($name) { public function read($name) {
$data = FALSE; $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 { try {
$raw = $this->connection->query('SELECT data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', array(':name' => $name), $this->options)->fetchField(); $raw = $this->connection->query('SELECT data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name', array(':name' => $name), $this->options)->fetchField();
if ($raw !== FALSE) { if ($raw !== FALSE) {
@ -81,6 +81,8 @@ class DatabaseStorage implements StorageInterface {
} }
} }
catch (\Exception $e) { 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; return $data;
} }
@ -89,10 +91,6 @@ class DatabaseStorage implements StorageInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function readMultiple(array $names) { 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(); $list = array();
try { try {
$list = $this->connection->query('SELECT name, data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name IN (:names)', array(':names' => $names), $this->options)->fetchAllKeyed(); $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 @@ class DatabaseStorage implements StorageInterface {
$data = $this->decode($data); $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; return $list;
} }
/** /**
* Implements Drupal\Core\Config\StorageInterface::write(). * {@inheritdoc}
*
* @throws PDOException
*
* @todo Ignore slave targets for data manipulation operations.
*/ */
public function write($name, array $data) { public function write($name, array $data) {
$data = $this->encode($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; $options = array('return' => Database::RETURN_AFFECTED) + $this->options;
return (bool) $this->connection->merge($this->table, $options) return (bool) $this->connection->merge($this->table, $options)
->key('name', $name) ->key('name', $name)
@ -121,6 +141,60 @@ class DatabaseStorage implements StorageInterface {
->execute(); ->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(). * Implements Drupal\Core\Config\StorageInterface::delete().
* *
@ -168,29 +242,31 @@ class DatabaseStorage implements StorageInterface {
} }
/** /**
* Implements Drupal\Core\Config\StorageInterface::listAll(). * {@inheritdoc}
*
* @throws PDOException
* @throws \Drupal\Core\Database\DatabaseExceptionWrapper
* Only thrown in case $this->options['throw_exception'] is TRUE.
*/ */
public function listAll($prefix = '') { public function listAll($prefix = '') {
return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name LIKE :name', array( try {
':name' => db_like($prefix) . '%', return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name LIKE :name', array(
), $this->options)->fetchCol(); ':name' => $this->connection->escapeLike($prefix) . '%',
), $this->options)->fetchCol();
}
catch (\Exception $e) {
return array();
}
} }
/** /**
* Implements Drupal\Core\Config\StorageInterface::deleteAll(). * {@inheritdoc}
*
* @throws PDOException
* @throws \Drupal\Core\Database\DatabaseExceptionWrapper
* Only thrown in case $this->options['throw_exception'] is TRUE.
*/ */
public function deleteAll($prefix = '') { public function deleteAll($prefix = '') {
$options = array('return' => Database::RETURN_AFFECTED) + $this->options; try {
return (bool) $this->connection->delete($this->table, $options) $options = array('return' => Database::RETURN_AFFECTED) + $this->options;
->condition('name', $prefix . '%', 'LIKE') return (bool) $this->connection->delete($this->table, $options)
->execute(); ->condition('name', $prefix . '%', 'LIKE')
->execute();
}
catch (\Exception $e) {
return FALSE;
}
} }
} }

View File

@ -68,6 +68,18 @@ class FileStorage implements StorageInterface {
return 'yml'; 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(). * Implements Drupal\Core\Config\StorageInterface::exists().
*/ */
@ -105,21 +117,23 @@ class FileStorage implements StorageInterface {
} }
/** /**
* Implements Drupal\Core\Config\StorageInterface::write(). * {@inheritdoc}
*
* @throws \Drupal\Core\Config\UnsupportedDataTypeConfigException
* @throws \Drupal\Core\Config\StorageException
*/ */
public function write($name, array $data) { public function write($name, array $data) {
try { try {
$data = $this->encode($data); $data = $this->encode($data);
} }
catch(DumpException $e) { 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); $target = $this->getFilePath($name);
$status = @file_put_contents($target, $data); $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) { if ($status === FALSE) {
throw new StorageException('Failed to write configuration file: ' . $this->getFilePath($name)); throw new StorageException('Failed to write configuration file: ' . $this->getFilePath($name));
} }
@ -213,7 +227,7 @@ class FileStorage implements StorageInterface {
// glob() silently ignores the error of a non-existing search directory, // glob() silently ignores the error of a non-existing search directory,
// even with the GLOB_ERR flag. // even with the GLOB_ERR flag.
if (!file_exists($this->directory)) { if (!file_exists($this->directory)) {
throw new StorageException($this->directory . '/ not found.'); return array();
} }
$extension = '.' . static::getFileExtension(); $extension = '.' . static::getFileExtension();
// \GlobIterator on Windows requires an absolute path. // \GlobIterator on Windows requires an absolute path.

View File

@ -131,6 +131,27 @@ abstract class StorableConfigBase extends ConfigBase {
return $this->schemaWrapper; 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. * Casts the value to correct data type using the configuration schema.
* *

View File

@ -41,7 +41,7 @@ interface StorageInterface {
/** /**
* Reads configuration data from the storage. * Reads configuration data from the storage.
* *
* @param array $name * @param array $names
* List of names of the configuration objects to load. * List of names of the configuration objects to load.
* *
* @return array * @return array
@ -60,6 +60,9 @@ interface StorageInterface {
* *
* @return bool * @return bool
* TRUE on success, FALSE in case of an error. * 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); public function write($name, array $data);

View File

@ -28,11 +28,11 @@ class UpdateServiceProvider implements ServiceProviderInterface, ServiceModifier
$container $container
->register('lock', 'Drupal\Core\Lock\NullLockBackend'); ->register('lock', 'Drupal\Core\Lock\NullLockBackend');
// Prevent config from accessing {cache_config}. // Prevent config from being accessed via a cache wrapper by removing
// @see $conf['cache_classes'], update_prepare_d8_bootstrap() // any existing definition and setting an alias to the actual storage.
$container $container->removeDefinition('config.storage');
->register('config.storage', 'Drupal\Core\Config\FileStorage') $container->setAlias('config.storage', 'config.storage.active');
->addArgument(config_get_config_directory(CONFIG_ACTIVE_DIRECTORY));
$container->register('module_handler', 'Drupal\Core\Extension\UpdateModuleHandler') $container->register('module_handler', 'Drupal\Core\Extension\UpdateModuleHandler')
->addArgument('%container.modules%'); ->addArgument('%container.modules%');
$container $container

View File

@ -12,6 +12,7 @@ use Drupal\Core\Config\ConfigManagerInterface;
use Drupal\Core\Config\StorageInterface; use Drupal\Core\Config\StorageInterface;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\system\FileDownloadController; use Drupal\system\FileDownloadController;
use Symfony\Component\Yaml\Dumper;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -81,13 +82,15 @@ class ConfigController implements ContainerInjectionInterface {
* Downloads a tarball of the site configuration. * Downloads a tarball of the site configuration.
*/ */
public function downloadExport() { 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'); $archiver = new ArchiveTar(file_directory_temp() . '/config.tar.gz', 'gz');
$config_dir = config_get_config_directory(); foreach (\Drupal::service('config.storage')->listAll() as $name) {
$config_files = array(); $archiver->addString("$name.yml", $dumper->dump(\Drupal::config($name)->get(), PHP_INT_MAX, 0, TRUE));
foreach (\Drupal::service('config.storage')->listAll() as $config_name) {
$config_files[] = $config_dir . '/' . $config_name . '.yml';
} }
$archiver->createModify($config_files, '', config_get_config_directory());
$request = new Request(array('file' => 'config.tar.gz')); $request = new Request(array('file' => 'config.tar.gz'));
return $this->fileDownloadController->download($request, 'temporary'); return $this->fileDownloadController->download($request, 'temporary');

View File

@ -12,6 +12,7 @@ use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormBase; use Drupal\Core\Form\FormBase;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Yaml\Dumper;
/** /**
* Provides a form for exporting a single configuration file. * Provides a form for exporting a single configuration file.
@ -32,6 +33,13 @@ class ConfigSingleExportForm extends FormBase {
*/ */
protected $configStorage; protected $configStorage;
/**
* The YAML dumper.
*
* @var \Symfony\Component\Yaml\Dumper
*/
protected $dumper;
/** /**
* Tracks the valid config entity type definitions. * Tracks the valid config entity type definitions.
* *
@ -46,10 +54,14 @@ class ConfigSingleExportForm extends FormBase {
* The entity manager. * The entity manager.
* @param \Drupal\Core\Config\StorageInterface $config_storage * @param \Drupal\Core\Config\StorageInterface $config_storage
* The 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->entityManager = $entity_manager;
$this->configStorage = $config_storage; $this->configStorage = $config_storage;
$this->dumper = $dumper;
$this->dumper->setIndentation(2);
} }
/** /**
@ -58,7 +70,8 @@ class ConfigSingleExportForm extends FormBase {
public static function create(ContainerInterface $container) { public static function create(ContainerInterface $container) {
return new static( return new static(
$container->get('entity.manager'), $container->get('entity.manager'),
$container->get('config.storage') $container->get('config.storage'),
new Dumper()
); );
} }
@ -151,8 +164,7 @@ class ConfigSingleExportForm extends FormBase {
$name = $form_state['values']['config_name']; $name = $form_state['values']['config_name'];
} }
// Read the raw data for this config name, encode it, and display it. // Read the raw data for this config name, encode it, and display it.
$data = $this->configStorage->read($name); $form['export']['#value'] = $this->dumper->dump($this->configStorage->read($name), PHP_INT_MAX);
$form['export']['#value'] = $this->configStorage->encode($data);
$form['export']['#description'] = $this->t('The filename is %name.', array('%name' => $name . '.yml')); $form['export']['#description'] = $this->t('The filename is %name.', array('%name' => $name . '.yml'));
return $form['export']; return $form['export'];
} }

View File

@ -11,6 +11,7 @@ use Drupal\Core\Config\StorageInterface;
use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\ConfirmFormBase;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Yaml\Yaml;
/** /**
* Provides a form for importing a single configuration file. * Provides a form for importing a single configuration file.
@ -31,6 +32,13 @@ class ConfigSingleImportForm extends ConfirmFormBase {
*/ */
protected $configStorage; protected $configStorage;
/**
* The YAML component.
*
* @var \Symfony\Component\Yaml\Yaml
*/
protected $yaml;
/** /**
* If the config exists, this is that object. Otherwise, FALSE. * If the config exists, this is that object. Otherwise, FALSE.
* *
@ -52,10 +60,13 @@ class ConfigSingleImportForm extends ConfirmFormBase {
* The entity manager. * The entity manager.
* @param \Drupal\Core\Config\StorageInterface $config_storage * @param \Drupal\Core\Config\StorageInterface $config_storage
* The 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->entityManager = $entity_manager;
$this->configStorage = $config_storage; $this->configStorage = $config_storage;
$this->yaml = $yaml;
} }
/** /**
@ -64,7 +75,8 @@ class ConfigSingleImportForm extends ConfirmFormBase {
public static function create(ContainerInterface $container) { public static function create(ContainerInterface $container) {
return new static( return new static(
$container->get('entity.manager'), $container->get('entity.manager'),
$container->get('config.storage') $container->get('config.storage'),
new Yaml()
); );
} }
@ -184,7 +196,7 @@ class ConfigSingleImportForm extends ConfirmFormBase {
} }
// Decode the submitted import. // Decode the submitted import.
$data = $this->configStorage->decode($form_state['values']['import']); $data = $this->yaml->parse($form_state['values']['import']);
// Validate for config entities. // Validate for config entities.
if ($form_state['values']['config_type'] !== 'system.simple') { if ($form_state['values']['config_type'] !== 'system.simple') {

View File

@ -11,6 +11,7 @@ use Drupal\Component\Utility\String;
use Drupal\Core\Config\ConfigNameException; use Drupal\Core\Config\ConfigNameException;
use Drupal\simpletest\DrupalUnitTestBase; use Drupal\simpletest\DrupalUnitTestBase;
use Drupal\Core\Config\FileStorage; use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\DatabaseStorage;
use Drupal\Core\Config\UnsupportedDataTypeConfigException; use Drupal\Core\Config\UnsupportedDataTypeConfigException;
/** /**
@ -192,10 +193,10 @@ class ConfigCRUDTest extends DrupalUnitTestBase {
*/ */
public function testDataTypes() { public function testDataTypes() {
\Drupal::moduleHandler()->install(array('config_test')); \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'; $name = 'config_test.types';
$config = $this->container->get('config.factory')->get($name); $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)); $this->verbose('<pre>' . $original_content . "\n" . var_export($storage->read($name), TRUE));
// Verify variable data types are intact. // Verify variable data types are intact.
@ -220,7 +221,7 @@ class ConfigCRUDTest extends DrupalUnitTestBase {
$this->assertIdentical($config->get(), $data); $this->assertIdentical($config->get(), $data);
// Assert the data against the file storage. // Assert the data against the file storage.
$this->assertIdentical($storage->read($name), $data); $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(). // Set data using config::setData().
$config->setData($data)->save(); $config->setData($data)->save();

View File

@ -8,7 +8,7 @@
namespace Drupal\config\Tests; namespace Drupal\config\Tests;
use Drupal\simpletest\WebTestBase; use Drupal\simpletest\WebTestBase;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Dumper;
/** /**
* Tests the user interface for importing/exporting a single configuration. * 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'); public static $modules = array('config', 'config_test');
/**
* The YAML dumper.
*
* @var \Symfony\Component\Yaml\Dumper
*/
protected $dumper;
public static function getInfo() { public static function getInfo() {
return array( return array(
'name' => 'Configuration Single Import/Export UI', 'name' => 'Configuration Single Import/Export UI',
@ -30,6 +37,15 @@ class ConfigSingleImportExportTest extends WebTestBase {
); );
} }
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->dumper = new Dumper();
$this->dumper->setIndentation(2);
}
/** /**
* Tests importing a single configuration file. * Tests importing a single configuration file.
*/ */
@ -111,12 +127,11 @@ EOD;
*/ */
public function testImportSimpleConfiguration() { public function testImportSimpleConfiguration() {
$this->drupalLogin($this->drupalCreateUser(array('import configuration'))); $this->drupalLogin($this->drupalCreateUser(array('import configuration')));
$yaml = new Yaml();
$config = \Drupal::config('system.site')->set('name', 'Test simple import'); $config = \Drupal::config('system.site')->set('name', 'Test simple import');
$edit = array( $edit = array(
'config_type' => 'system.simple', 'config_type' => 'system.simple',
'config_name' => $config->getName(), '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->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'))); $this->assertRaw(t('Are you sure you want to update the %name @type?', array('%name' => $config->getName(), '@type' => 'simple configuration')));
@ -152,7 +167,7 @@ EOD;
$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.'); $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'); $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.'); $this->assertFieldByXPath('//textarea[@name="export"]', $data, 'The fallback date format config entity export code is displayed.');
} }

View File

@ -95,6 +95,10 @@ abstract class ConfigStorageTestBase extends DrupalUnitTestBase {
$result = $this->invalidStorage->read($name); $result = $this->invalidStorage->read($name);
$this->assertIdentical($result, FALSE); $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 // Deleting all names with prefix deletes the appropriate data and returns
// TRUE. // TRUE.
$files = array( $files = array(
@ -111,15 +115,6 @@ abstract class ConfigStorageTestBase extends DrupalUnitTestBase {
$this->assertIdentical($result, TRUE); $this->assertIdentical($result, TRUE);
$this->assertIdentical($names, array()); $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. // Deleting from a non-existing storage bin throws an exception.
try { try {
@ -131,16 +126,6 @@ abstract class ConfigStorageTestBase extends DrupalUnitTestBase {
$this->pass($class . ' thrown upon deleting from a non-existing storage bin.'); $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. // Test renaming an object that does not exist throws an exception.
try { try {
$this->storage->rename('config_test.storage_does_not_exist', 'config_test.storage_does_not_exist_rename'); $this->storage->rename('config_test.storage_does_not_exist', 'config_test.storage_does_not_exist_rename');
@ -159,6 +144,10 @@ abstract class ConfigStorageTestBase extends DrupalUnitTestBase {
$this->pass($class . ' thrown upon renaming a nonexistent storage bin.'); $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'));
} }
/** /**

View File

@ -2,7 +2,7 @@
/** /**
* @file * @file
* Definition of Drupal\config\Tests\Storage\DatabaseStorageTest. * Contains \Drupal\config\Tests\Storage\DatabaseStorageTest.
*/ */
namespace Drupal\config\Tests\Storage; namespace Drupal\config\Tests\Storage;
@ -24,28 +24,6 @@ class DatabaseStorageTest extends ConfigStorageTestBase {
function setUp() { function setUp() {
parent::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->storage = new DatabaseStorage($this->container->get('database'), 'config');
$this->invalidStorage = new DatabaseStorage($this->container->get('database'), 'invalid'); $this->invalidStorage = new DatabaseStorage($this->container->get('database'), 'invalid');

View File

@ -29,6 +29,7 @@ class FileStorageTest extends ConfigStorageTestBase {
// FileStorage::listAll() requires other configuration data to exist. // FileStorage::listAll() requires other configuration data to exist.
$this->storage->write('system.performance', \Drupal::config('system.performance')->get()); $this->storage->write('system.performance', \Drupal::config('system.performance')->get());
$this->storage->write('core.extension', array('module' => array()));
} }
protected function read($name) { protected function read($name) {

View File

@ -368,7 +368,6 @@ class ConfigTranslationUiTest extends WebTestBase {
*/ */
public function testDateFormatTranslation() { public function testDateFormatTranslation() {
$this->drupalLogin($this->admin_user); $this->drupalLogin($this->admin_user);
$file_storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]);
$this->drupalGet('admin/config/regional/date-time'); $this->drupalGet('admin/config/regional/date-time');

View File

@ -229,8 +229,9 @@ abstract class DrupalUnitTestBase extends UnitTestBase {
$container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory'); $container->register('cache_factory', 'Drupal\Core\Cache\MemoryBackendFactory');
$container $container
->register('config.storage', 'Drupal\Core\Config\FileStorage') ->register('config.storage.active', 'Drupal\Core\Config\DatabaseStorage')
->addArgument($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); ->addArgument(Database::getConnection())
->addArgument('config');
$this->settingsSet('keyvalue_default', 'keyvalue.memory'); $this->settingsSet('keyvalue_default', 'keyvalue.memory');
$container->set('keyvalue.memory', $this->keyValueFactory); $container->set('keyvalue.memory', $this->keyValueFactory);

View File

@ -600,6 +600,19 @@ ini_set('session.cookie_lifetime', 2000000);
*/ */
# $cookie_domain = '.example.com'; # $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. * Configuration overrides.
* *