diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index aa0e022..b7673f3 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -9,6 +9,7 @@ use Drupal\Core\Database\Database; use Drupal\Core\Database\Connection; +use Drupal\Core\Database\SchemaObjectExistsException; /** * Defines the Database storage. @@ -56,9 +57,18 @@ 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(); + // 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 { + 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) { + return FALSE; + } } /** @@ -72,8 +82,7 @@ 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. + // catch the exception and just return FALSE so the caller can handle it. 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) { @@ -100,7 +109,7 @@ public function readMultiple(array $names) { $data = $this->decode($data); } } - catch (Exception $e) { + catch (\Exception $e) { } return $list; } @@ -114,6 +123,30 @@ public function readMultiple(array $names) { */ 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 +155,56 @@ 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 PDOException + */ + 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; + } + return FALSE; + } + + /** + * Defines the schema for the configuration table. + */ + public 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 @@ -175,9 +258,18 @@ public function decode($raw) { * Only thrown in case $this->options['throw_exception'] is TRUE. */ public function listAll($prefix = '') { - 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(); + // 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 { + 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(); + } } /** diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php index 7b3643c..fd7be56 100644 --- a/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php +++ b/core/modules/config/lib/Drupal/config/Form/ConfigSingleImportForm.php @@ -59,8 +59,8 @@ class ConfigSingleImportForm extends ConfirmFormBase { * The entity manager. * @param \Drupal\Core\Config\StorageInterface $config_storage * The config storage. - * @param \Symfony\Component\Yaml\Parser $parser - * The yaml dumper. + * @param \Symfony\Component\Yaml\Yaml $yaml + * The YAML component. */ public function __construct(EntityManagerInterface $entity_manager, StorageInterface $config_storage, Yaml $yaml) { $this->entityManager = $entity_manager; diff --git a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php index 93108a4..2be5b37 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/DrupalUnitTestBase.php @@ -103,15 +103,9 @@ protected function beforePrepareEnvironment() { * * @see config_get_config_directory() */ - protected function prepareConfigStorage() { + protected function prepareConfigDirectories() { $this->configDirectories = array(); include_once DRUPAL_ROOT . '/core/includes/install.inc'; - // Create the configuration table. - module_load_install('system'); - $schema = system_schema(); - // We use a different table name here and in the container to avoid - // exceptions when system module is installed. - Database::getConnection()->schema()->createTable('test_base_config', $schema['config']); foreach (array(CONFIG_ACTIVE_DIRECTORY, CONFIG_STAGING_DIRECTORY) as $type) { // Assign the relative path to the global variable. $path = $this->siteDirectory . '/config_' . $type; @@ -133,6 +127,9 @@ protected function setUp() { parent::setUp(); + // Create and set new configuration directories. + $this->prepareConfigDirectories(); + // Build a minimal, partially mocked environment for unit tests. $this->containerBuild(\Drupal::getContainer()); // Make sure it survives kernel rebuilds. @@ -140,8 +137,7 @@ protected function setUp() { \Drupal::state()->set('system.module.files', $this->moduleFiles); \Drupal::state()->set('system.theme.files', $this->themeFiles); - // Create and set new configuration directories. - $this->prepareConfigStorage(); + // Bootstrap the kernel. // No need to dump it; this test runs in-memory. $this->kernel = new DrupalKernel('unit_testing', drupal_classloader(), FALSE); @@ -231,7 +227,7 @@ public function containerBuild(ContainerBuilder $container) { $container ->register('config.storage', 'Drupal\Core\Config\DatabaseStorage') ->addArgument(Database::getConnection()) - ->addArgument('test_base_config'); + ->addArgument('config'); $this->settingsSet('keyvalue_default', 'keyvalue.memory'); $container->set('keyvalue.memory', $this->keyValueFactory); diff --git a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php index 3e5e9a4..aaf9612 100644 --- a/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/DrupalKernel/DrupalKernelTest.php @@ -30,7 +30,7 @@ function setUp() { // invoke DrupalUnitTestBase::setUp(), since that would set up further // environment aspects, which would distort this test, because it tests // the DrupalKernel (re-)building itself. - $this->prepareConfigStorage(); + $this->prepareConfigDirectories(); $this->settingsSet('php_storage', array('service_container' => array( 'bin' => 'service_container', diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 0bd1942..ca5d128 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -970,25 +970,7 @@ function system_schema() { 'source_langcode_pid' => array('source', 'langcode', 'pid'), ), ); - $schema['config'] = 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'), - ); + $schema['config_snapshot'] = array( 'description' => 'Stores a snapshot of the last imported configuration.', 'fields' => array(