diff --git a/core/core.services.yml b/core/core.services.yml index 42b5614..fd909d8 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -58,6 +58,8 @@ services: config.storage: class: Drupal\Core\Config\CachedStorage arguments: ['@config.cachedstorage.storage', '@cache.config'] + tags: + - { name: persist } config.context.factory: class: Drupal\Core\Config\Context\ConfigContextFactory arguments: ['@event_dispatcher'] diff --git a/core/lib/Drupal/Core/Config/CachedStorage.php b/core/lib/Drupal/Core/Config/CachedStorage.php index 7d094b4..f290843 100644 --- a/core/lib/Drupal/Core/Config/CachedStorage.php +++ b/core/lib/Drupal/Core/Config/CachedStorage.php @@ -33,6 +33,13 @@ class CachedStorage implements StorageInterface { protected $cache; /** + * List of listAll() prefixes with their results. + * + * @var array + */ + protected static $listAllCache = array(); + + /** * Constructs a new CachedStorage controller. * * @param Drupal\Core\Config\StorageInterface $storage @@ -80,6 +87,32 @@ public function read($name) { } /** + * Implements Drupal\Core\Config\StorageInterface::readMultiple(). + */ + public function readMultiple(array $names) { + $remaining_names = $names; + $list = array(); + $cached_list = $this->cache->getMultiple($remaining_names); + + // The cache backend removed names that were successfully loaded from the + // cache. + if (!empty($remaining_names)) { + $list = $this->storage->readMultiple($remaining_names); + // Cache configuration objects that were loaded from the storage. + foreach ($list as $name => $data) { + $this->cache->set($name, $data, CacheBackendInterface::CACHE_PERMANENT); + } + } + + // Add the configuration objects from the cache to the list. + foreach ($cached_list as $name => $cache) { + $list[$name] = $cache->data; + } + + return $list; + } + + /** * Implements Drupal\Core\Config\StorageInterface::write(). */ public function write($name, array $data) { @@ -87,6 +120,8 @@ public function write($name, array $data) { // While not all written data is read back, setting the cache instead of // just deleting it avoids cache rebuild stampedes. $this->cache->set($name, $data, CacheBackendInterface::CACHE_PERMANENT); + $this->cache->deleteTags(array('listAll' => TRUE)); + static::$listAllCache = array(); return TRUE; } return FALSE; @@ -100,6 +135,8 @@ public function delete($name) { // rebuilding the cache before the storage is gone. if ($this->storage->delete($name)) { $this->cache->delete($name); + $this->cache->deleteTags(array('listAll' => TRUE)); + static::$listAllCache = array(); return TRUE; } return FALSE; @@ -114,6 +151,8 @@ public function rename($name, $new_name) { if ($this->storage->rename($name, $new_name)) { $this->cache->delete($name); $this->cache->delete($new_name); + $this->cache->deleteTags(array('listAll' => TRUE)); + static::$listAllCache = array(); return TRUE; } return FALSE; @@ -135,11 +174,24 @@ public function decode($raw) { /** * Implements Drupal\Core\Config\StorageInterface::listAll(). - * - * Not supported by CacheBackendInterface. */ public function listAll($prefix = '') { - return $this->storage->listAll($prefix); + // Check the static cache first. + if (!isset(static::$listAllCache[$prefix])) { + + // The : character is not allowed in config file names, so this can not + // conflict. + // @todo: Maintain a single cache entry for this, similar to a cache + // collector? + if ($cache = $this->cache->get('list:' . $prefix)) { + static::$listAllCache[$prefix] = $cache->data; + } + else { + static::$listAllCache[$prefix] = $this->storage->listAll($prefix); + $this->cache->set('list:' . $prefix, static::$listAllCache[$prefix], CacheBackendInterface::CACHE_PERMANENT, array('listAll' => TRUE)); + } + } + return static::$listAllCache[$prefix]; } /** diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php index 740f316..02afbd1 100644 --- a/core/lib/Drupal/Core/Config/Config.php +++ b/core/lib/Drupal/Core/Config/Config.php @@ -108,6 +108,25 @@ public function init() { } /** + * Initializes a configuration object with pre-loaded data. + * + * @param array $data + * Array of loaded data for this configuration object. + * + * @return Drupal\Core\Config\Config + * The configuration object. + */ + public function initWithData(array $data) { + $this->isLoaded = TRUE; + $this->overrides = array(); + $this->isNew = FALSE; + $this->notify('init'); + $this->replaceData($data); + $this->notify('load'); + return $this; + } + + /** * Returns the name of this configuration object. * * @return string diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php index 0c3785c..2027832 100644 --- a/core/lib/Drupal/Core/Config/ConfigFactory.php +++ b/core/lib/Drupal/Core/Config/ConfigFactory.php @@ -85,6 +85,45 @@ public function get($name) { } /** + * Returns a list of configuration objects for a given names and context. + * + * This will pre-load all requested configuration objects does not create + * new configuration objects. + * + * @param array $names + * List of names of configuration objects. + * + * @return array + * List of successfully loaded configuration objects, keyed by name. + */ + public function loadMultiple(array $names) { + $context = $this->getContext(); + + $list = array(); + foreach ($names as $key => $name) { + $cache_key = $this->getCacheKey($name, $context); + // @todo: Deleted configuration stays in $this->cache, only return + // config entities that are not new. + if (isset($this->cache[$cache_key]) && !$this->cache[$cache_key]->isNew()) { + $list[$name] = $this->cache[$cache_key]; + unset($names[$key]); + } + } + + // Pre-load remaining configuration files. + if (!empty($names)) { + $storage_data = $this->storage->readMultiple($names); + foreach ($storage_data as $name => $data) { + $cache_key = $this->getCacheKey($name, $context); + $this->cache[$cache_key] = new Config($name, $this->storage, $context); + $this->cache[$cache_key]->initWithData($data); + $list[$name] = $this->cache[$cache_key]; + } + } + return $list; + } + + /** * Resets and re-initializes configuration objects. Internal use only. * * @param string $name diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php index 3d43a8c..5779b31 100644 --- a/core/lib/Drupal/Core/Config/DatabaseStorage.php +++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php @@ -87,6 +87,29 @@ public function read($name) { } /** + * Implements Drupal\Core\Config\StorageInterface::read(). + * + * + * @param 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(); + try { + $list = $this->connection->query('SELECT name, data FROM {' . $this->connection->escapeTable($this->table) . '} WHERE name IN (:names)', array(':names' => $names), $this->options)->fetchAllKeyed(); + foreach ($list as &$data) { + $data = $this->decode($data); + } + } + catch (Exception $e) { + } + return $list; + } + + /** * Implements Drupal\Core\Config\StorageInterface::write(). * * @throws PDOException diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php index 5487e04..f719720 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php @@ -226,24 +226,20 @@ protected function buildQuery($ids, $revision_id = FALSE) { // Load all of the configuration entities. if ($ids === NULL) { $names = drupal_container()->get('config.storage')->listAll($prefix); - $result = array(); - foreach ($names as $name) { - $config = config($name); - $result[$config->get($this->idKey)] = new $config_class($config->get(), $this->entityType); - } - return $result; } else { - $result = array(); + $names = array(); foreach ($ids as $id) { // Add the prefix to the ID to serve as the configuration object name. - $config = config($prefix . $id); - if (!$config->isNew()) { - $result[$id] = new $config_class($config->get(), $this->entityType); - } + $names[] = $prefix . $id; } - return $result; } + + $result = array(); + foreach (\Drupal::service('config.factory')->loadMultiple($names) as $config) { + $result[$config->get($this->idKey)] = new $config_class($config->get(), $this->entityType); + } + return $result; } /** diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php index 6422e9d..7021c25 100644 --- a/core/lib/Drupal/Core/Config/FileStorage.php +++ b/core/lib/Drupal/Core/Config/FileStorage.php @@ -90,6 +90,21 @@ public function read($name) { } /** + * Implements Drupal\Core\Config\StorageInterface::readMultiple(). + * + * @throws Symfony\Component\Yaml\Exception\ParseException + */ + public function readMultiple(array $names) { + $list = array(); + foreach ($names as $name) { + if ($data = $this->read($name)) { + $list[$name] = $data; + } + } + return $list; + } + + /** * Implements Drupal\Core\Config\StorageInterface::write(). * * @throws Symfony\Component\Yaml\Exception\DumpException diff --git a/core/lib/Drupal/Core/Config/NullStorage.php b/core/lib/Drupal/Core/Config/NullStorage.php index 336c111..2bf121d 100644 --- a/core/lib/Drupal/Core/Config/NullStorage.php +++ b/core/lib/Drupal/Core/Config/NullStorage.php @@ -38,6 +38,13 @@ public function read($name) { } /** + * Implements Drupal\Core\Config\StorageInterface::readMultiple(). + */ + public function readMultiple(array $names) { + return array(); + } + + /** * Implements Drupal\Core\Config\StorageInterface::write(). */ public function write($name, array $data) { diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php index ceeecba..77eca0a 100644 --- a/core/lib/Drupal/Core/Config/StorageInterface.php +++ b/core/lib/Drupal/Core/Config/StorageInterface.php @@ -39,6 +39,18 @@ public function exists($name); public function read($name); /** + * Reads configuration data from the storage. + * + * @param ayrray $name + * List of names of the configuration objects to load. + * + * @return array|bool + * A list of the configuration data stored for the configuration object name + * that could be loaded for the passed list of names. + */ + public function readMultiple(array $names); + + /** * Writes configuration data to the storage. * * @param string $name