diff --git a/core/lib/Drupal/Core/Config/CachedStorage.php b/core/lib/Drupal/Core/Config/CachedStorage.php index c27d2c6..a5d11fd 100644 --- a/core/lib/Drupal/Core/Config/CachedStorage.php +++ b/core/lib/Drupal/Core/Config/CachedStorage.php @@ -41,13 +41,6 @@ class CachedStorage implements StorageInterface, StorageCacheInterface { protected $findByPrefixCache = array(); /** - * The cache prefix to use for all cache entries. - * - * @var string - */ - protected $cachePrefix = NULL; - - /** * Constructs a new CachedStorage. * * @param \Drupal\Core\Config\StorageInterface $storage @@ -74,7 +67,7 @@ public function exists($name) { * Implements Drupal\Core\Config\StorageInterface::read(). */ public function read($name) { - if ($cache = $this->cache->get($this->getCacheKey($name))) { + if ($cache = $this->cache->get($name)) { // The cache contains either the cached configuration data or FALSE // if the configuration file does not exist. return $cache->data; @@ -82,7 +75,7 @@ public function read($name) { // Read from the storage on a cache miss and cache the data. Also cache // information about missing configuration objects. $data = $this->storage->read($name); - $this->cache->set($this->getCacheKey($name), $data); + $this->cache->set($name, $data); return $data; } @@ -94,24 +87,23 @@ public function readMultiple(array $names) { // The names array is passed by reference and will only contain the names of // config object not found after the method call. // @see \Drupal\Core\Cache\CacheBackendInterface::getMultiple() - $cids = $this->getCacheKeys($names); - $cached_list = $this->cache->getMultiple($cids); + $cached_list = $this->cache->getMultiple($names); - if (!empty($cids)) { - $list = $this->storage->readMultiple(array_keys($cids)); + if (!empty($names)) { + $list = $this->storage->readMultiple($names); // Cache configuration objects that were loaded from the storage, cache // missing configuration objects as an explicit FALSE. $items = array(); foreach ($names as $name) { - $items[$this->getCacheKey($name)] = array('data' => isset($list[$name]) ? $list[$name] : FALSE); + $items[$name] = array('data' => isset($list[$name]) ? $list[$name] : FALSE); } $this->cache->setMultiple($items); } // Add the configuration objects from the cache to the list. - foreach ($cached_list as $cid => $cache) { - $list[$this->getNameFromCacheKey($cid)] = $cache->data; + foreach ($cached_list as $name => $cache) { + $list[$name] = $cache->data; } // Ensure that only existing configuration objects are returned, filter out @@ -126,8 +118,7 @@ public function write($name, array $data) { if ($this->storage->write($name, $data)) { // While not all written data is read back, setting the cache instead of // just deleting it avoids cache rebuild stampedes. - $this->cache->set($this->getCacheKey($name), $data); - // @todo: How to deal with the prefix for the tag? + $this->cache->set($name, $data); Cache::deleteTags(array($this::FIND_BY_PREFIX_CACHE_TAG => TRUE)); $this->findByPrefixCache = array(); return TRUE; @@ -142,7 +133,7 @@ public function delete($name) { // If the cache was the first to be deleted, another process might start // rebuilding the cache before the storage is gone. if ($this->storage->delete($name)) { - $this->cache->delete($this->getCacheKey($name)); + $this->cache->delete($name); Cache::deleteTags(array($this::FIND_BY_PREFIX_CACHE_TAG => TRUE)); $this->findByPrefixCache = array(); return TRUE; @@ -157,8 +148,8 @@ public function rename($name, $new_name) { // If the cache was the first to be deleted, another process might start // rebuilding the cache before the storage is renamed. if ($this->storage->rename($name, $new_name)) { - $this->cache->delete($this->getCacheKey($name)); - $this->cache->delete($this->getCacheKey($new_name)); + $this->cache->delete($name); + $this->cache->delete($new_name); Cache::deleteTags(array($this::FIND_BY_PREFIX_CACHE_TAG => TRUE)); $this->findByPrefixCache = array(); return TRUE; @@ -211,13 +202,13 @@ protected function findByPrefix($prefix) { if (!isset($this->findByPrefixCache[$prefix])) { // The : character is not allowed in config file names, so this can not // conflict. - if ($cache = $this->cache->get($this->getCacheKey('find:' . $prefix))) { + if ($cache = $this->cache->get('find:' . $prefix)) { $this->findByPrefixCache[$prefix] = $cache->data; } else { $this->findByPrefixCache[$prefix] = $this->storage->listAll($prefix); $this->cache->set( - $this->getCacheKey('find:' . $prefix), + 'find:' . $prefix, $this->findByPrefixCache[$prefix], Cache::PERMANENT, array($this::FIND_BY_PREFIX_CACHE_TAG => TRUE) @@ -233,9 +224,9 @@ protected function findByPrefix($prefix) { public function deleteAll($prefix = '') { // If the cache was the first to be deleted, another process might start // rebuilding the cache before the storage is renamed. - $names = $this->storage->listAll($prefix); + $cids = $this->storage->listAll($prefix); if ($this->storage->deleteAll($prefix)) { - $this->cache->deleteMultiple($this->getCacheKeys($names)); + $this->cache->deleteMultiple($cids); return TRUE; } return FALSE; @@ -247,58 +238,4 @@ public function deleteAll($prefix = '') { public function resetListCache() { $this->findByPrefixCache = array(); } - - /** - * Builds the cache key for a config name. - * - * @param string $name - * Name of the config file. - * - * @return string - * The cache key. - */ - protected function getCacheKey($name) { - return $this->cachePrefix ? $this->cachePrefix . ':' . $name : $name; - } - - /** - * Builds the cache key for a list of config names. - * - * @param array $names - * List of config file names. - * - * @return array - * List of cache keys, keyed by config name. - */ - protected function getCacheKeys(array $names) { - $cids = array(); - foreach ($names as $name) { - $cids[$name] = $this->getCacheKey($name); - } - return $cids; - } - - /** - * Returns the config name from a cache key. - * - * @param string $cid - * Cache key - * - * @return string - * Name of the config file. - */ - protected function getNameFromCacheKey($cid) { - return $this->cachePrefix ? substr($cid, strlen($this->cachePrefix) + 1) : $cid; - } - - /** - * Set the cache prefix used by this storage. - * - * @param string $cachePrefix - * Name of the prefix that should be used for all cache IDs. - */ - public function setCachePrefix($cachePrefix) { - $this->cachePrefix = $cachePrefix; - } - } diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index 11ab691..c7d1cd2 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -73,18 +73,10 @@ public function exists($name) { * {@inheritdoc} */ 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(); - if ($raw !== FALSE) { - $data = $this->decode($raw); - } + $data = $this->readMultiple(array($name)); + if (isset($data[$name])) { + return $data[$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; } /** diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index facba21..1dac0f4 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -215,14 +215,4 @@ public function deleteAll($prefix = '') { return $success; } - - /** - * Sets the filesystem path for configuration objects. - * - * @param string $directory - * The filesystem path for configuration objects. - */ - public function setDirectory($directory) { - $this->directory = $directory; - } } diff --git a/core/modules/language/language.install b/core/modules/language/language.install index 8825db3..7f529d5 100644 --- a/core/modules/language/language.install +++ b/core/modules/language/language.install @@ -5,10 +5,18 @@ * Install, update and uninstall functions for the language module. */ +use Drupal\language\Config\LanguageOverrideDatabaseStorage; + /** * Implements hook_install(). */ function language_install() { + // Make sure that the config storage table is created. + $storage = \Drupal::service('language.config_storage'); + if ($storage instanceof LanguageOverrideDatabaseStorage) { + $storage->ensureTableExists(); + } + // Get all the existing modules that are enabled and install any language // configuration. $language_config_override = \Drupal::service('language.config_factory_override'); diff --git a/core/modules/language/language.services.yml b/core/modules/language/language.services.yml index 48c7c82..6aa5b2f 100644 --- a/core/modules/language/language.services.yml +++ b/core/modules/language/language.services.yml @@ -18,11 +18,9 @@ services: factory_method: get factory_service: cache_factory arguments: [language_config] - language.config_storage.file: - class: Drupal\language\Config\LanguageOverrideFileStorage language.config_storage: - class: Drupal\language\Config\LanguageOverrideCachedStorage - arguments: ['@language.config_storage.file', '@language.cache_config'] + class: Drupal\language\Config\LanguageOverrideDatabaseStorage + arguments: ['@database', 'config_language'] language.config_factory_override: class: Drupal\language\Config\LanguageConfigFactoryOverride arguments: ['@language.config_storage', '@event_dispatcher', '@config.typed'] diff --git a/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverride.php b/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverride.php index 37bdf9e..ed769fd 100644 --- a/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverride.php +++ b/core/modules/language/lib/Drupal/language/Config/LanguageConfigFactoryOverride.php @@ -133,7 +133,7 @@ public function setLanguageFromDefault(LanguageDefault $language_default = NULL) */ public function install($type, $name) { // Work out if this extension provides default language overrides. - $config_dir = drupal_get_path($type, $name) . '/config/language'; + $config_dir = drupal_get_path($type, $name) . '/config/install/language'; if (is_dir($config_dir)) { // List all the directories. // \DirectoryIterator on Windows requires an absolute path. @@ -158,22 +158,7 @@ public function install($type, $name) { * @todo maybe this should be done somewhere else? */ public function uninstall($type, $name) { - // Can not use ConfigurableLanguageManager::getLanguages() since that would - // create a circular dependency. - $language_directory = config_get_config_directory() .'/language'; - if (is_dir(($language_directory))) { - $it = new \DirectoryIterator(realpath($language_directory)); - foreach ($it as $dir) { - if (!$dir->isDot() && $dir->isDir() ) { - $this->storage->setLangcode($dir->getFilename()); - $config_names = $this->storage->listAll($name . '.'); - foreach ($config_names as $config_name) { - $config = new LanguageConfigOverride($config_name, $this->storage, $this->typedConfigManager); - $config->delete(); - } - } - } - } + $this->storage->deleteAllLanguages($name . '.'); } } diff --git a/core/modules/language/lib/Drupal/language/Config/LanguageOverrideCachedStorage.php b/core/modules/language/lib/Drupal/language/Config/LanguageOverrideCachedStorage.php deleted file mode 100644 index 4026102..0000000 --- a/core/modules/language/lib/Drupal/language/Config/LanguageOverrideCachedStorage.php +++ /dev/null @@ -1,55 +0,0 @@ -cachePrefix = $langcode; - $this->storage->setLangcode($langcode); - return $this; - } - - /** - * {@inheritdoc} - */ - function __clone() { - // Also clone the inner storage, so that language code changes work as - // expected. - $this->storage = clone $this->storage; - } - -} diff --git a/core/modules/language/lib/Drupal/language/Config/LanguageOverrideDatabaseStorage.php b/core/modules/language/lib/Drupal/language/Config/LanguageOverrideDatabaseStorage.php new file mode 100644 index 0000000..ae8fcb6 --- /dev/null +++ b/core/modules/language/lib/Drupal/language/Config/LanguageOverrideDatabaseStorage.php @@ -0,0 +1,171 @@ +connection->queryRange('SELECT 1 FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name = :name AND langcode = :langcode', 0, 1, array( + ':name' => $name, + ':langcode' => $this->langcode, + ), $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; + } + } + + /** + * {@inheritdoc} + */ + 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) AND langcode = :langcode', array(':names' => $names, ':langcode' => $this->langcode), $this->options)->fetchAllKeyed(); + foreach ($list as &$data) { + $data = $this->decode($data); + } + } + 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; + } + + /** + * {@inheritdoc} + */ + protected function doWrite($name, $data) { + $options = array('return' => Database::RETURN_AFFECTED) + $this->options; + return (bool) $this->connection->merge($this->table, $options) + ->keys(array('name' => $name, 'langcode' => $this->langcode)) + ->fields(array('data' => $data)) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function ensureTableExists() { + return parent::ensureTableExists(); + } + + /** + * {@inheritdoc} + */ + protected static function schemaDefinition() { + $schema = parent::schemaDefinition(); + $schema['fields']['langcode'] = array( + 'description' => 'The {language}.langcode of this configuration data.', + 'type' => 'varchar', + 'length' => 12, + 'not null' => TRUE, + ); + $schema['primary key'] = array('name', 'langcode'); + return $schema; + } + + /** + * {@inheritdoc} + */ + public function delete($name) { + $options = array('return' => Database::RETURN_AFFECTED) + $this->options; + return (bool) $this->connection->delete($this->table, $options) + ->condition('name', $name) + ->condition('langcode', $this->langcode) + ->execute(); + } + + + /** + * {@inheritdoc} + */ + public function rename($name, $new_name) { + $options = array('return' => Database::RETURN_AFFECTED) + $this->options; + return (bool) $this->connection->update($this->table, $options) + ->fields(array('name' => $new_name)) + ->condition('name', $name) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function listAll($prefix = '') { + try { + return $this->connection->query('SELECT name FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name LIKE :name AND langcode = :langcode', array( + ':name' => $this->connection->escapeLike($prefix) . '%', + ':langcode' => $this->langcode, + ), $this->options)->fetchCol(); + } + catch (\Exception $e) { + return array(); + } + } + + /** + * {@inheritdoc} + */ + public function deleteAll($prefix = '') { + try { + $options = array('return' => Database::RETURN_AFFECTED) + $this->options; + return (bool) $this->connection->delete($this->table, $options) + ->condition('name', $prefix . '%', 'LIKE') + ->condition('langcode', $this->langcode) + ->execute(); + } + catch (\Exception $e) { + return FALSE; + } + } + + + /** + * {@inheritdoc} + */ + public function deleteAllLanguages($prefix) { + 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; + } + } + + /** + * {@inheritdoc} + */ + public function setLangcode($langcode) { + $this->langcode = $langcode; + return $this; + } + +} diff --git a/core/modules/language/lib/Drupal/language/Config/LanguageOverrideFileStorage.php b/core/modules/language/lib/Drupal/language/Config/LanguageOverrideFileStorage.php deleted file mode 100644 index fa31003..0000000 --- a/core/modules/language/lib/Drupal/language/Config/LanguageOverrideFileStorage.php +++ /dev/null @@ -1,128 +0,0 @@ -ensureDirectory(); - return parent::write($name, $data); - } - - /** - * {@inheritdoc} - */ - public function delete($name) { - if ($this->hasDirectory()) { - return parent::delete($name); - } - return FALSE; - } - - /** - * {@inheritdoc} - */ - public function listAll($prefix = '') { - if ($this->hasDirectory()) { - return parent::listAll($prefix); - } - return array(); - } - - /** - * Creates a directory if necessary. - */ - protected function ensureDirectory() { - if (!$this->hasDirectory()) { - drupal_mkdir($this->directory, NULL, TRUE); - $this->directoryExists = TRUE; - } - } - - /** - * {@inheritdoc} - */ - public function setLangcode($langcode) { - // Reset directory check. - $this->directoryExists = NULL; - $this->langcode = $langcode; - $this->directory = $this->getDirectory($langcode); - if (empty($this->storage)) { - $this->storage = new FileStorage($this->directory); - } - else { - $this->storage->setDirectory($this->directory); - } - return $this; - } - - /** - * Discovers is the directory for the language exists. - * - * @return bool - * TRUE if the directory exists, FALSE if not. - */ - protected function hasDirectory() { - if (!isset($this->directoryExists)) { - $this->directoryExists = is_dir($this->directory); - } - return $this->directoryExists; - } - -} diff --git a/core/modules/language/lib/Drupal/language/Config/LanguageOverrideStorageInterface.php b/core/modules/language/lib/Drupal/language/Config/LanguageOverrideStorageInterface.php index ba62497..f2a369a 100644 --- a/core/modules/language/lib/Drupal/language/Config/LanguageOverrideStorageInterface.php +++ b/core/modules/language/lib/Drupal/language/Config/LanguageOverrideStorageInterface.php @@ -7,7 +7,6 @@ namespace Drupal\language\Config; -use Drupal\Core\Config\StorageCacheInterface; use Drupal\Core\Config\StorageInterface; interface LanguageOverrideStorageInterface extends StorageInterface { @@ -23,4 +22,18 @@ */ public function setLangcode($langcode); + /** + * Deletes all configuration in all languages for the given prefix. + * + * @param string $prefix + * (optional) The prefix to search for. If omitted, all configuration + * objects that exist will be deleted. + * + * @return boolean + * TRUE on success, FALSE otherwise. + * + * @see \Drupal\Core\Config\StorageInterface::deleteAll() + */ + public function deleteAllLanguages($prefix); + } diff --git a/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php b/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php index f25a66f..6c60af4 100644 --- a/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/CachedStorageTest.php @@ -84,41 +84,6 @@ public function testGetMultipleOnPrimedCache() { } /** - * Test that the cache prefix is used correctly. - */ - public function testGetMultipleCachePrefix() { - $configNames = array( - 'foo.bar', - 'baz.back', - ); - $configCacheValues = array( - 'prefix:foo.bar' => array( - 'foo' => 'bar', - ), - 'prefix:baz.back' => array( - 'foo' => 'bar', - ), - ); - $configValues = array( - 'foo.bar' => array( - 'foo' => 'bar', - ), - 'baz.back' => array( - 'foo' => 'bar', - ), - ); - $storage = $this->getMock('Drupal\Core\Config\StorageInterface'); - $storage->expects($this->never())->method('readMultiple'); - $cache = new MemoryBackend(__FUNCTION__); - foreach ($configCacheValues as $key => $value) { - $cache->set($key, $value); - } - $cachedStorage = new CachedStorage($storage, $cache); - $cachedStorage->setCachePrefix('prefix'); - $this->assertEquals($configValues, $cachedStorage->readMultiple($configNames)); - } - - /** * Test fall through to file storage in CachedStorage::readMulitple(). */ public function testGetMultipleOnPartiallyPrimedCache() { @@ -151,7 +116,7 @@ public function testGetMultipleOnPartiallyPrimedCache() { $storage = $this->getMock('Drupal\Core\Config\StorageInterface'); $storage->expects($this->once()) ->method('readMultiple') - ->with(array($configNames[2], $configNames[4])) + ->with(array(2 => $configNames[2], 4 => $configNames[4])) ->will($this->returnValue($response)); $cachedStorage = new CachedStorage($storage, $cache);