diff --git a/core/core.services.yml b/core/core.services.yml index 8e203ed..1564692 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -38,13 +38,6 @@ services: factory_method: get factory_service: cache_factory arguments: [bootstrap] - cache.config: - class: Drupal\Core\Cache\CacheBackendInterface - tags: - - { name: cache.bin } - factory_method: get - factory_service: cache_factory - arguments: [config] cache.default: class: Drupal\Core\Cache\CacheBackendInterface tags: @@ -115,7 +108,7 @@ services: class: Drupal\Core\Config\InstallStorage config.typed: class: Drupal\Core\Config\TypedConfigManager - arguments: ['@config.storage', '@config.storage.schema', '@cache.config'] + arguments: ['@config.storage', '@config.storage.schema', '@cache.default'] cron: class: Drupal\Core\Cron arguments: ['@module_handler', '@lock', '@queue', '@state', '@current_user', '@session_manager'] diff --git a/core/lib/Drupal/Core/Config/CachedStorage.php b/core/lib/Drupal/Core/Config/CachedStorage.php index a5d11fd..9fe13db 100644 --- a/core/lib/Drupal/Core/Config/CachedStorage.php +++ b/core/lib/Drupal/Core/Config/CachedStorage.php @@ -8,7 +8,7 @@ namespace Drupal\Core\Config; use Drupal\Core\Cache\Cache; -use Drupal\Core\Cache\CacheBackendInterface; +use Drupal\Core\Cache\CacheFactoryInterface; /** * Defines the cached storage. @@ -27,6 +27,13 @@ class CachedStorage implements StorageInterface, StorageCacheInterface { protected $storage; /** + * The cache factory. + * + * @var \Drupal\Core\Cache\CacheFactoryInterface + */ + protected $cacheFactory; + + /** * The instantiated Cache backend. * * @var \Drupal\Core\Cache\CacheBackendInterface @@ -45,12 +52,15 @@ class CachedStorage implements StorageInterface, StorageCacheInterface { * * @param \Drupal\Core\Config\StorageInterface $storage * A configuration storage to be cached. - * @param \Drupal\Core\Cache\CacheBackendInterface $cache - * A cache backend instance to use for caching. + * @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory + * A cache factory used for getting cache backends. + * @param string $collection + * (optional) The collection to store configuration in. */ - public function __construct(StorageInterface $storage, CacheBackendInterface $cache) { + public function __construct(StorageInterface $storage, CacheFactoryInterface $cache_factory, $collection = '') { $this->storage = $storage; - $this->cache = $cache; + $this->cacheFactory = $cache_factory; + $this->setCollection($collection); } /** @@ -238,4 +248,26 @@ public function deleteAll($prefix = '') { public function resetListCache() { $this->findByPrefixCache = array(); } + + /** + * {@inheritdoc} + */ + public function setCollection($collection) { + if (empty($collection)) { + $bin = 'config'; + } + else { + $bin = 'config_' . str_replace('.', '_', $collection); + } + $this->cache = $this->cacheFactory->get($bin); + $this->storage->setCollection($collection); + } + + /** + * {@inheritdoc} + */ + public function getCollections() { + return $this->storage->getCollections(); + } + } diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index 11ab691..2aa29f2 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -38,6 +38,13 @@ class DatabaseStorage implements StorageInterface { protected $options = array(); /** + * The storage collection. + * + * @var string + */ + protected $collection = ''; + + /** * Constructs a new DatabaseStorage. * * @param \Drupal\Core\Database\Connection $connection @@ -46,11 +53,14 @@ class DatabaseStorage implements StorageInterface { * A database table name to store configuration data in. * @param array $options * (optional) Any additional database connection options to use in queries. + * @param string $collection + * (optional) The collection to store configuration in. */ - public function __construct(Connection $connection, $table, array $options = array()) { + public function __construct(Connection $connection, $table, array $options = array(), $collection = '') { $this->connection = $connection; $this->table = $table; $this->options = $options; + $this->collection = $collection; } /** @@ -75,7 +85,7 @@ public function exists($name) { public function read($name) { $data = FALSE; 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 collection = :collection AND name = :name', array(':collection' => $this->collection, ':name' => $name), $this->options)->fetchField(); if ($raw !== FALSE) { $data = $this->decode($raw); } @@ -93,7 +103,7 @@ public function read($name) { public function readMultiple(array $names) { $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(); + $list = $this->connection->query('SELECT name, data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name IN (:names)', array(':collection' => $this->collection, ':names' => $names), $this->options)->fetchAllKeyed(); foreach ($list as &$data) { $data = $this->decode($data); } @@ -136,7 +146,7 @@ public function write($name, array $data) { protected function doWrite($name, $data) { $options = array('return' => Database::RETURN_AFFECTED) + $this->options; return (bool) $this->connection->merge($this->table, $options) - ->key('name', $name) + ->keys(array('collection', 'name'), array($this->collection, $name)) ->fields(array('data' => $data)) ->execute(); } @@ -176,8 +186,15 @@ protected static function schemaDefinition() { $schema = array( 'description' => 'The base table for configuration data.', 'fields' => array( + 'collection' => array( + 'description' => 'Primary Key: Config object collection.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), 'name' => array( - 'description' => 'Primary Key: Unique config object name.', + 'description' => 'Primary Key: Config object name.', 'type' => 'varchar', 'length' => 255, 'not null' => TRUE, @@ -190,7 +207,7 @@ protected static function schemaDefinition() { 'size' => 'big', ), ), - 'primary key' => array('name'), + 'primary key' => array('collection', 'name'), ); return $schema; } @@ -205,6 +222,7 @@ protected static function schemaDefinition() { public function delete($name) { $options = array('return' => Database::RETURN_AFFECTED) + $this->options; return (bool) $this->connection->delete($this->table, $options) + ->condition('collection', $this->collection) ->condition('name', $name) ->execute(); } @@ -220,6 +238,7 @@ public function rename($name, $new_name) { return (bool) $this->connection->update($this->table, $options) ->fields(array('name' => $new_name)) ->condition('name', $name) + ->condition('collection', $this->collection) ->execute(); } @@ -246,7 +265,8 @@ public function decode($raw) { */ public function listAll($prefix = '') { try { - return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name LIKE :name', array( + return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection = :collection AND name LIKE :name', array( + ':collection' => $this->collection, ':name' => $this->connection->escapeLike($prefix) . '%', ), $this->options)->fetchCol(); } @@ -263,10 +283,27 @@ public function deleteAll($prefix = '') { $options = array('return' => Database::RETURN_AFFECTED) + $this->options; return (bool) $this->connection->delete($this->table, $options) ->condition('name', $prefix . '%', 'LIKE') + ->condition('collection', $this->collection) ->execute(); } catch (\Exception $e) { return FALSE; } } + + /** + * {@inheritdoc} + */ + public function setCollection($collection) { + $this->collection = $collection; + } + + /** + * {@inheritdoc} + */ + public function getCollections() { + return $this->connection->query('SELECT DISTINCT collection FROM {' . $this->connection->escapeTable($this->table) . '} WHERE collection <> \'\' ORDER by collection')->fetchCol(); + } + + } diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index 1dac0f4..4f08e49 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -17,6 +17,13 @@ class FileStorage implements StorageInterface { /** + * The storage collection. + * + * @var string + */ + protected $collection; + + /** * The filesystem path for configuration objects. * * @var string @@ -28,9 +35,12 @@ class FileStorage implements StorageInterface { * * @param string $directory * A directory path to use for reading and writing of configuration files. + * @param string $collection + * (optional) The collection to store configuration in. */ - public function __construct($directory) { + public function __construct($directory, $collection = '') { $this->directory = $directory; + $this->collection = $this->setCollection($collection); } /** @@ -40,7 +50,7 @@ public function __construct($directory) { * The path to the configuration file. */ public function getFilePath($name) { - return $this->directory . '/' . $name . '.' . static::getFileExtension(); + return $this->getCollectionDirectory() . '/' . $name . '.' . static::getFileExtension(); } /** @@ -57,10 +67,14 @@ 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); + $dir = $this->getCollectionDirectory(); + $success = file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS); + // Only create .htaccess file in root driectory. + if ($dir == $this->directory) { + $success = $success && file_save_htaccess($this->directory, TRUE, TRUE); + } if (!$success) { - throw new StorageException("Failed to create config directory {$this->directory}"); + throw new StorageException("Failed to create config directory {$dir}"); } return $this; } @@ -142,12 +156,22 @@ public function write($name, array $data) { */ public function delete($name) { if (!$this->exists($name)) { - if (!file_exists($this->directory)) { - throw new StorageException($this->directory . '/ not found.'); + $dir = $this->getCollectionDirectory(); + if (!file_exists($dir)) { + throw new StorageException($dir . '/ not found.'); } return FALSE; } - return drupal_unlink($this->getFilePath($name)); + $success = drupal_unlink($this->getFilePath($name)); + + // If a collection is now empty remove the directory. + if ($success && !empty($this->collection)) { + $names = $this->listAll(); + if (empty($names)) { + drupal_rmdir($this->getCollectionDirectory()); + } + } + return $success; } /** @@ -186,12 +210,13 @@ public function decode($raw) { 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)) { + $dir = $this->getCollectionDirectory(); + if (!file_exists($dir)) { return array(); } $extension = '.' . static::getFileExtension(); // \GlobIterator on Windows requires an absolute path. - $files = new \GlobIterator(realpath($this->directory) . '/' . $prefix . '*' . $extension); + $files = new \GlobIterator(realpath($dir) . '/' . $prefix . '*' . $extension); $names = array(); foreach ($files as $file) { @@ -215,4 +240,60 @@ public function deleteAll($prefix = '') { return $success; } + + /** + * {@inheritdoc} + */ + public function setCollection($collection) { + $this->collection = $collection; + } + + /** + * {@inheritdoc} + * + * @param string $directory + * (optional) The directory to check for sub directories. This allows this + * function to be used recursively to discover all the collections in the + * storage. Defaults to an empty string which starts checking from the + * storage's root directory. + */ + public function getCollections($directory = '') { + $collections = array(); + if (empty($directory)) { + $directory = $this->directory; + } + foreach (new \DirectoryIterator($directory) as $fileinfo) { + if ($fileinfo->isDir() && !$fileinfo->isDot()) { + $collection = $fileinfo->getFilename(); + $sub_collections = $this->getCollections($directory . '/' . $collection); + if (empty($sub_collections)) { + $collections[] = $collection; + } + else { + foreach ($sub_collections as $sub_collection) { + $collections[] = $collection . '.' . $sub_collection; + } + } + } + } + sort($collections); + return $collections; + } + + /** + * Gets the directory for the collection. + * + * @return string + * The directory for the collection. + */ + protected function getCollectionDirectory() { + if (empty($this->collection)) { + $dir = $this->directory; + } + else { + $dir = $this->directory . '/' . str_replace('.', '/', $this->collection); + } + return $dir; + } + } diff --git a/core/lib/Drupal/Core/Config/NullStorage.php b/core/lib/Drupal/Core/Config/NullStorage.php index c66f718..7914bd9 100644 --- a/core/lib/Drupal/Core/Config/NullStorage.php +++ b/core/lib/Drupal/Core/Config/NullStorage.php @@ -92,4 +92,19 @@ public function listAll($prefix = '') { public function deleteAll($prefix = '') { return FALSE; } + + /** + * {@inheritdoc} + */ + public function setCollection($collection) { + // No op. + } + + /** + * {@inheritdoc} + */ + public function getCollections() { + return array(); + } + } diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index 8b99784..d6251b4 100644 --- a/core/lib/Drupal/Core/Config/StorageInterface.php +++ b/core/lib/Drupal/Core/Config/StorageInterface.php @@ -156,4 +156,29 @@ public function listAll($prefix = ''); */ public function deleteAll($prefix = ''); + /** + * Sets the collection on the storage. + * + * @param string $collection + * The collection key. If not set then this is normal configuration + * storage. However if set the storage needs to use it to differentiate the + * collections. collections can be nested for example 'language.de'. If this + * is a file system then we could create a language folder with a subfolder + * named de. If this is a database table then there could be a collection + * column which will store the value 'language.de'. + */ + public function setCollection($collection); + + /** + * Gets the existing collections. + * + * A configuration storage can contain multiple sets of configuration objects + * in partitioned collections. The collection key name identifies the current + * collection used. + * + * @return array + * An array of existing collections. + */ + public function getCollections(); + } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php index ac77a58..21ee6c5 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRecreateTest.php @@ -42,7 +42,6 @@ public static function getInfo() { public function setUp() { parent::setUp(); - $this->installSchema('system', 'config_snapshot'); $this->installSchema('node', 'node'); $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php index a538c12..f2ca9aa 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportRenameValidationTest.php @@ -51,7 +51,6 @@ public static function getInfo() { public function setUp() { parent::setUp(); - $this->installSchema('system', 'config_snapshot'); $this->installSchema('node', 'node'); // Set up the ConfigImporter object for testing. diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php index 341d3d4..b3c1633 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImporterTest.php @@ -43,8 +43,6 @@ public static function getInfo() { function setUp() { parent::setUp(); - $this->installSchema('system', 'config_snapshot'); - $this->installConfig(array('config_test')); // Installing config_test's default configuration pollutes the global // variable being used for recording hook invocations by this test already, diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php index 7c6c1cb..7359a35 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigOverrideTest.php @@ -31,7 +31,6 @@ public static function getInfo() { public function setUp() { parent::setUp(); - $this->installSchema('system', 'config_snapshot'); $this->copyConfig($this->container->get('config.storage'), $this->container->get('config.storage.staging')); } diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php index 295ff74..a0b3ecc 100644 --- a/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSnapshotTest.php @@ -32,7 +32,6 @@ public static function getInfo() { public function setUp() { parent::setUp(); - $this->installSchema('system', 'config_snapshot'); // Update the config snapshot. This allows the parent::setUp() to write // configuration files. \Drupal::service('config.manager')->createSnapshot(\Drupal::service('config.storage'), \Drupal::service('config.storage.snapshot')); diff --git a/core/modules/config/lib/Drupal/config/Tests/DefaultConfigTest.php b/core/modules/config/lib/Drupal/config/Tests/DefaultConfigTest.php index 9ce5cea..3a5b5d4 100644 --- a/core/modules/config/lib/Drupal/config/Tests/DefaultConfigTest.php +++ b/core/modules/config/lib/Drupal/config/Tests/DefaultConfigTest.php @@ -43,7 +43,7 @@ public function testDefaultConfig() { $typed_config = new TypedConfigManager( \Drupal::service('config.storage'), new TestInstallStorage(InstallStorage::CONFIG_SCHEMA_DIRECTORY), - \Drupal::service('cache.config') + \Drupal::service('cache.default') ); // Create a configuration storage with access to default configuration in diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/CachedStorageTest.php b/core/modules/config/lib/Drupal/config/Tests/Storage/CachedStorageTest.php new file mode 100644 index 0000000..47f6891 --- /dev/null +++ b/core/modules/config/lib/Drupal/config/Tests/Storage/CachedStorageTest.php @@ -0,0 +1,120 @@ + 'CachedStorage operations', + 'description' => 'Tests CachedStorage operations.', + 'group' => 'Configuration', + ); + } + + function setUp() { + parent::setUp(); + $this->filestorage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); + $this->storage = new CachedStorage($this->filestorage, \Drupal::service('cache_factory')); + $this->cache = \Drupal::service('cache_factory')->get('config'); + // ::listAll() verifications require other configuration data to exist. + $this->storage->write('system.performance', array()); + } + + /** + * {@inheritdoc} + */ + public function testInvalidStorage() { + // No-op as this test does not make sense. + } + + /** + * {@inheritdoc} + */ + public function testInvalidData() { + // No-op as this test does not make sense. + } + + /** + * {@inheritdoc} + */ + protected function read($name) { + $data = $this->cache->get($name); + // Cache misses fall through to the underlying storage. + return $data ? $data->data : $this->filestorage->read($name); + } + + /** + * {@inheritdoc} + */ + protected function insert($name, $data) { + // Try writing to the file storage first catch exceptions so that we can do + // the invalid data tests. + try { + $this->filestorage->write($name, $data); + $this->cache->set($name, $data); + } + catch (\Exception $e) { + } + } + + /** + * {@inheritdoc} + */ + protected function update($name, $data) { + // Try writing to the file storage first catch exceptions so that we can do + // the invalid data tests. + try { + $this->filestorage->write($name, $data); + $this->cache->set($name, $data); + } + catch (\Exception $e) { + } + } + + /** + * {@inheritdoc} + */ + protected function delete($name) { + $this->cache->delete($name); + unlink($this->filestorage->getFilePath($name)); + } + + /** + * {@inheritdoc} + */ + public function containerBuild(ContainerBuilder $container) { + parent::containerBuild($container); + // Use the regular database cache backend to aid testing. + $container->register('cache_factory', 'Drupal\Core\Cache\DatabaseBackendFactory') + ->addArgument(Database::getConnection()); + } + +} 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 925ecbd..6ad56d8 100644 --- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php +++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php @@ -24,6 +24,11 @@ abstract class ConfigStorageTestBase extends DrupalUnitTestBase { /** + * @var \Drupal\Core\Config\StorageInterface; + */ + protected $storage; + + /** * Tests storage CRUD operations. * * @todo Coverage: Trigger PDOExceptions / Database exceptions. @@ -38,17 +43,6 @@ function testCRUD() { $data = $this->storage->read($name); $this->assertIdentical($data, FALSE); - // Reading a name containing non-decodeable data returns FALSE. - $this->insert($name, ''); - $data = $this->storage->read($name); - $this->assertIdentical($data, FALSE); - - $this->update($name, 'foo'); - $data = $this->storage->read($name); - $this->assertIdentical($data, FALSE); - - $this->delete($name); - // Writing data returns TRUE and the data has been written. $data = array('foo' => 'bar'); $result = $this->storage->write($name, $data); @@ -90,14 +84,6 @@ function testCRUD() { $result = $this->storage->delete($name); $this->assertIdentical($result, FALSE); - // Reading from a non-existing storage bin returns FALSE. - $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( @@ -114,35 +100,57 @@ function testCRUD() { $this->assertIdentical($result, TRUE); $this->assertIdentical($names, array()); - - // Deleting from a non-existing storage bin throws an exception. + // Test renaming an object that does not exist throws an exception. try { - $this->invalidStorage->delete($name); - $this->fail('Exception not thrown upon deleting from a non-existing storage bin.'); + $this->storage->rename('config_test.storage_does_not_exist', 'config_test.storage_does_not_exist_rename'); } catch (\Exception $e) { $class = get_class($e); - $this->pass($class . ' thrown upon deleting from a non-existing storage bin.'); + $this->pass($class . ' thrown upon renaming a nonexistent storage bin.'); } - // Test renaming an object that does not exist throws an exception. + // Test renaming to an object that already exists throws an exception. try { - $this->storage->rename('config_test.storage_does_not_exist', 'config_test.storage_does_not_exist_rename'); + $this->storage->rename('system.cron', 'system.performance'); } catch (\Exception $e) { $class = get_class($e); $this->pass($class . ' thrown upon renaming a nonexistent storage bin.'); } + } - // Test renaming to an object that already exists throws an exception. + /** + * Tests an invalid storage. + */ + public function testInvalidStorage() { + $name = 'config_test.storage'; + + // Write something to the valid storage to prove that the storages do not + // pollute one another. + $data = array('foo' => 'bar'); + $result = $this->storage->write($name, $data); + $this->assertIdentical($result, TRUE); + + $raw_data = $this->read($name); + $this->assertIdentical($raw_data, $data); + + // Reading from a non-existing storage bin returns FALSE. + $result = $this->invalidStorage->read($name); + $this->assertIdentical($result, FALSE); + + // Deleting from a non-existing storage bin throws an exception. try { - $this->storage->rename('system.cron', 'system.performance'); + $this->invalidStorage->delete($name); + $this->fail('Exception not thrown upon deleting from a non-existing storage bin.'); } catch (\Exception $e) { $class = get_class($e); - $this->pass($class . ' thrown upon renaming a nonexistent storage bin.'); + $this->pass($class . ' thrown upon deleting from a non-existing storage bin.'); } + // Listing on a non-existing storage bin returns an empty array. + $result = $this->invalidStorage->listAll(); + $this->assertIdentical($result, array()); // Writing to a non-existing storage bin creates the bin. $this->invalidStorage->write($name, array('foo' => 'bar')); $result = $this->invalidStorage->read($name); @@ -150,6 +158,22 @@ function testCRUD() { } /** + * Test that invalid data can not be read from the storage. + */ + public function testInvalidData() { + $name = 'config_test.storage'; + + // Reading a name containing non-decodeable data returns FALSE. + $this->insert($name, ''); + $data = $this->storage->read($name); + $this->assertIdentical($data, FALSE); + + $this->update($name, 'foo'); + $data = $this->storage->read($name); + $this->assertIdentical($data, FALSE); + } + + /** * Tests storage writing and reading data preserving data type. */ function testDataTypes() { @@ -180,4 +204,57 @@ function testDataTypes() { abstract protected function update($name, $data); abstract protected function delete($name); + + public function testCollection() { + $name = 'config_test.storage'; + $data = array('foo => bar'); + $result = $this->storage->write($name, $data); + $this->assertIdentical($result, TRUE); + $this->assertIdentical($data, $this->storage->read($name)); + + // Create configuration in a new collection. + $this->storage->setCollection('collection.new'); + $this->storage->write($name, $data); + $this->assertIdentical($result, TRUE); + $this->assertIdentical($data, $this->storage->read($name)); + $new_data = array('foo' => 'baz'); + $this->storage->write($name, $new_data); + $this->assertIdentical($result, TRUE); + $this->assertIdentical($new_data, $this->storage->read($name)); + + // Create configuration in another collection. + $this->storage->setCollection('collection.another'); + $this->storage->write($name, $new_data); + $this->assertIdentical($result, TRUE); + $this->assertIdentical($new_data, $this->storage->read($name)); + + // Create configuration in yet another collection. + $this->storage->setCollection('alternate'); + $this->storage->write($name, $new_data); + $this->assertIdentical($result, TRUE); + $this->assertIdentical($new_data, $this->storage->read($name)); + + // Switch back to the collection-less mode and check the data still exists + // add has not been touched. + $this->storage->setCollection(''); + $this->assertIdentical($data, $this->storage->read($name)); + + // Check that the getCollections() method works. + $this->assertIdentical(array('alternate', 'collection.another', 'collection.new'), $this->storage->getCollections()); + + // Check that the collections are removed when they are empty. + $this->storage->setCollection('alternate'); + $this->storage->delete($name); + $this->assertIdentical(array('collection.another', 'collection.new'), $this->storage->getCollections()); + + // Check that the having an empty collection-less storage does not break + // anything. + $this->storage->setCollection(''); + // Before deleting check that the previous delete did not affect data in + // another collection. + $this->assertIdentical($data, $this->storage->read($name)); + $this->storage->delete($name); + $this->assertIdentical(array('collection.another', 'collection.new'), $this->storage->getCollections()); + } + } diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php b/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php index 5f77d92..5717a23 100644 --- a/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php +++ b/core/modules/field/lib/Drupal/field/Tests/FieldUnitTestBase.php @@ -37,7 +37,7 @@ function setUp() { parent::setUp(); $this->installSchema('entity_test', 'entity_test'); - $this->installSchema('system', array('sequences', 'config_snapshot')); + $this->installSchema('system', array('sequences')); $this->installSchema('user', array('users', 'users_roles')); // Set default storage backend and configure the theme system. diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml index af97659..c6e9838 100644 --- a/core/modules/locale/locale.services.yml +++ b/core/modules/locale/locale.services.yml @@ -1,7 +1,7 @@ services: locale.config.typed: class: Drupal\locale\LocaleConfigManager - arguments: ['@config.storage', '@config.storage.schema', '@config.storage.installer', '@locale.storage', '@cache.config', '@config.factory', '@language_manager'] + arguments: ['@config.storage', '@config.storage.schema', '@config.storage.installer', '@locale.storage', '@cache.default', '@config.factory', '@language_manager'] locale.storage: class: Drupal\locale\StringDatabaseStorage arguments: ['@database'] diff --git a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php index bdd2b92..f848870 100644 --- a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportChangeTest.php @@ -26,9 +26,6 @@ class NodeImportChangeTest extends DrupalUnitTestBase { */ public function setUp() { parent::setUp(); - - $this->installSchema('system', array('config_snapshot')); - // Set default storage backend. $this->installConfig(array('field', 'node_test_config')); } diff --git a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php index 1d59ca1..cbc2920 100644 --- a/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php +++ b/core/modules/node/lib/Drupal/node/Tests/Config/NodeImportCreateTest.php @@ -26,8 +26,6 @@ class NodeImportCreateTest extends DrupalUnitTestBase { */ public function setUp() { parent::setUp(); - - $this->installSchema('system', array('config_snapshot')); $this->installSchema('user', array('users')); // Set default storage backend. diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 820efa7..a14cd7b 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -956,25 +956,5 @@ function system_schema() { ), ); - $schema['config_snapshot'] = array( - 'description' => 'Stores a snapshot of the last imported configuration.', - 'fields' => array( - 'name' => array( - 'description' => 'The identifier for the config object (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 object.', - 'type' => 'blob', - 'not null' => TRUE, - 'size' => 'big', - ), - ), - 'primary key' => array('name'), - ); - return $schema; } diff --git a/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php b/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php index 6c60af4..e751e08 100644 --- a/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php @@ -15,6 +15,11 @@ */ class CachedStorageTest extends UnitTestCase { + /** + * @var \Drupal\Core\Cache\CacheFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheFactory; + public static function getInfo() { return array( 'name' => 'Config cached storage test', @@ -23,6 +28,10 @@ public static function getInfo() { ); } + public function setUp() { + $this->cacheFactory = $this->getMock('Drupal\Core\Cache\CacheFactoryInterface'); + } + /** * Test listAll static cache. */ @@ -37,7 +46,11 @@ public function testListAllStaticCache() { ->will($this->returnValue($response)); $cache = new NullBackend(__FUNCTION__); - $cachedStorage = new CachedStorage($storage, $cache); + $this->cacheFactory->expects($this->once()) + ->method('get') + ->with('config') + ->will($this->returnValue($cache)); + $cachedStorage = new CachedStorage($storage, $this->cacheFactory); $this->assertEquals($response, $cachedStorage->listAll($prefix)); $this->assertEquals($response, $cachedStorage->listAll($prefix)); } @@ -53,7 +66,11 @@ public function testListAllPrimedPersistentCache() { $response = array("$prefix." . $this->randomName(), "$prefix." . $this->randomName()); $cache = new MemoryBackend(__FUNCTION__); $cache->set('find:' . $prefix, $response); - $cachedStorage = new CachedStorage($storage, $cache); + $this->cacheFactory->expects($this->once()) + ->method('get') + ->with('config') + ->will($this->returnValue($cache)); + $cachedStorage = new CachedStorage($storage, $this->cacheFactory); $this->assertEquals($response, $cachedStorage->listAll($prefix)); } @@ -79,7 +96,11 @@ public function testGetMultipleOnPrimedCache() { foreach ($configCacheValues as $key => $value) { $cache->set($key, $value); } - $cachedStorage = new CachedStorage($storage, $cache); + $this->cacheFactory->expects($this->once()) + ->method('get') + ->with('config') + ->will($this->returnValue($cache)); + $cachedStorage = new CachedStorage($storage, $this->cacheFactory); $this->assertEquals($configCacheValues, $cachedStorage->readMultiple($configNames)); } @@ -119,7 +140,11 @@ public function testGetMultipleOnPartiallyPrimedCache() { ->with(array(2 => $configNames[2], 4 => $configNames[4])) ->will($this->returnValue($response)); - $cachedStorage = new CachedStorage($storage, $cache); + $this->cacheFactory->expects($this->once()) + ->method('get') + ->with('config') + ->will($this->returnValue($cache)); + $cachedStorage = new CachedStorage($storage, $this->cacheFactory); $expected_data = $configCacheValues + array($configNames[2] => $config_exists_not_cached_data); $this->assertEquals($expected_data, $cachedStorage->readMultiple($configNames)); @@ -143,7 +168,11 @@ public function testReadNonExistentFileCacheMiss() { ->method('read') ->with($name) ->will($this->returnValue(FALSE)); - $cachedStorage = new CachedStorage($storage, $cache); + $this->cacheFactory->expects($this->once()) + ->method('get') + ->with('config') + ->will($this->returnValue($cache)); + $cachedStorage = new CachedStorage($storage, $this->cacheFactory); $this->assertFalse($cachedStorage->read($name)); @@ -163,7 +192,11 @@ public function testReadNonExistentFileCached() { $storage = $this->getMock('Drupal\Core\Config\StorageInterface'); $storage->expects($this->never()) ->method('read'); - $cachedStorage = new CachedStorage($storage, $cache); + $this->cacheFactory->expects($this->once()) + ->method('get') + ->with('config') + ->will($this->returnValue($cache)); + $cachedStorage = new CachedStorage($storage, $this->cacheFactory); $this->assertFalse($cachedStorage->read($name)); }