CachedStorage.php

Same filename and directory in other branches
  1. 9 core/lib/Drupal/Core/Config/CachedStorage.php
  2. 8.9.x core/lib/Drupal/Core/Config/CachedStorage.php
  3. 10 core/lib/Drupal/Core/Config/CachedStorage.php

Namespace

Drupal\Core\Config

File

core/lib/Drupal/Core/Config/CachedStorage.php

View source
<?php

namespace Drupal\Core\Config;

use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;

/**
 * Defines the cached storage.
 *
 * The class gets another storage and a cache backend injected. It reads from
 * the cache and delegates the read to the storage on a cache miss. It also
 * handles cache invalidation.
 */
class CachedStorage implements StorageInterface, StorageCacheInterface {
    use DependencySerializationTrait;
    
    /**
     * The configuration storage to be cached.
     *
     * @var \Drupal\Core\Config\StorageInterface
     */
    protected $storage;
    
    /**
     * The instantiated Cache backend.
     *
     * @var \Drupal\Core\Cache\CacheBackendInterface
     */
    protected $cache;
    
    /**
     * List of listAll() prefixes with their results.
     *
     * @var array
     */
    protected $findByPrefixCache = [];
    
    /**
     * Constructs a new CachedStorage.
     *
     * @param \Drupal\Core\Config\StorageInterface $storage
     *   A configuration storage to be cached.
     * @param \Drupal\Core\Cache\CacheBackendInterface $cache
     *   A cache backend used to store configuration.
     */
    public function __construct(StorageInterface $storage, CacheBackendInterface $cache) {
        $this->storage = $storage;
        $this->cache = $cache;
    }
    
    /**
     * {@inheritdoc}
     */
    public function exists($name) {
        // The cache would read in the entire data (instead of only checking whether
        // any data exists), and on a potential cache miss, an additional storage
        // lookup would have to happen, so check the storage directly.
        return $this->storage
            ->exists($name);
    }
    
    /**
     * {@inheritdoc}
     */
    public function read($name) {
        $cache_key = $this->getCacheKey($name);
        if ($cache = $this->cache
            ->get($cache_key)) {
            // The cache contains either the cached configuration data or FALSE
            // if the configuration file does not exist.
            return $cache->data;
        }
        // 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($cache_key, $data);
        return $data;
    }
    
    /**
     * {@inheritdoc}
     */
    public function readMultiple(array $names) {
        $data_to_return = [];
        $cache_keys_map = $this->getCacheKeys($names);
        $cache_keys = array_values($cache_keys_map);
        $cached_list = $this->cache
            ->getMultiple($cache_keys);
        if (!empty($cache_keys)) {
            // $cache_keys_map contains the full $name => $cache_key map, while
            // $cache_keys contains just the $cache_key values that weren't found in
            // the cache.
            // @see \Drupal\Core\Cache\CacheBackendInterface::getMultiple()
            $names_to_get = array_keys(array_intersect($cache_keys_map, $cache_keys));
            $list = $this->storage
                ->readMultiple($names_to_get);
            // Cache configuration objects that were loaded from the storage, cache
            // missing configuration objects as an explicit FALSE.
            $items = [];
            foreach ($names_to_get as $name) {
                $data = $list[$name] ?? FALSE;
                $data_to_return[$name] = $data;
                $items[$cache_keys_map[$name]] = [
                    'data' => $data,
                ];
            }
            $this->cache
                ->setMultiple($items);
        }
        // Add the configuration objects from the cache to the list.
        $cache_keys_inverse_map = array_flip($cache_keys_map);
        foreach ($cached_list as $cache_key => $cache) {
            $name = $cache_keys_inverse_map[$cache_key];
            $data_to_return[$name] = $cache->data;
        }
        // Ensure that only existing configuration objects are returned, filter out
        // cached information about missing objects.
        return array_filter($data_to_return);
    }
    
    /**
     * {@inheritdoc}
     */
    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);
            $this->findByPrefixCache = [];
            return TRUE;
        }
        return FALSE;
    }
    
    /**
     * {@inheritdoc}
     */
    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->findByPrefixCache = [];
            return TRUE;
        }
        return FALSE;
    }
    
    /**
     * {@inheritdoc}
     */
    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->findByPrefixCache = [];
            return TRUE;
        }
        return FALSE;
    }
    
    /**
     * {@inheritdoc}
     */
    public function encode($data) {
        return $this->storage
            ->encode($data);
    }
    
    /**
     * {@inheritdoc}
     */
    public function decode($raw) {
        return $this->storage
            ->decode($raw);
    }
    
    /**
     * {@inheritdoc}
     */
    public function listAll($prefix = '') {
        // Do not cache when a prefix is not provided.
        if ($prefix) {
            return $this->findByPrefix($prefix);
        }
        return $this->storage
            ->listAll();
    }
    
    /**
     * Finds configuration object names starting with a given prefix.
     *
     * Given the following configuration objects:
     * - node.type.article
     * - node.type.page
     *
     * Passing the prefix 'node.type.' will return an array containing the above
     * names.
     *
     * @param string $prefix
     *   The prefix to search for
     *
     * @return array
     *   An array containing matching configuration object names.
     */
    protected function findByPrefix($prefix) {
        $cache_key = $this->getCacheKey($prefix);
        if (!isset($this->findByPrefixCache[$cache_key])) {
            $this->findByPrefixCache[$cache_key] = $this->storage
                ->listAll($prefix);
        }
        return $this->findByPrefixCache[$cache_key];
    }
    
    /**
     * {@inheritdoc}
     */
    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);
        if ($this->storage
            ->deleteAll($prefix)) {
            $this->cache
                ->deleteMultiple($this->getCacheKeys($names));
            return TRUE;
        }
        return FALSE;
    }
    
    /**
     * Clears the static list cache.
     */
    public function resetListCache() {
        $this->findByPrefixCache = [];
    }
    
    /**
     * {@inheritdoc}
     */
    public function createCollection($collection) {
        return new static($this->storage
            ->createCollection($collection), $this->cache);
    }
    
    /**
     * {@inheritdoc}
     */
    public function getAllCollectionNames() {
        return $this->storage
            ->getAllCollectionNames();
    }
    
    /**
     * {@inheritdoc}
     */
    public function getCollectionName() {
        return $this->storage
            ->getCollectionName();
    }
    
    /**
     * Returns a cache key for a configuration name using the collection.
     *
     * @param string $name
     *   The configuration name.
     *
     * @return string
     *   The cache key for the configuration name.
     */
    protected function getCacheKey($name) {
        return $this->getCollectionPrefix() . $name;
    }
    
    /**
     * Returns a cache key map for an array of configuration names.
     *
     * @param array $names
     *   The configuration names.
     *
     * @return array
     *   An array of cache keys keyed by configuration names.
     */
    protected function getCacheKeys(array $names) {
        $prefix = $this->getCollectionPrefix();
        $cache_keys = array_map(function ($name) use ($prefix) {
            return $prefix . $name;
        }, $names);
        return array_combine($names, $cache_keys);
    }
    
    /**
     * Returns a cache ID prefix to use for the collection.
     *
     * @return string
     *   The cache ID prefix.
     */
    protected function getCollectionPrefix() {
        $collection = $this->storage
            ->getCollectionName();
        if ($collection == StorageInterface::DEFAULT_COLLECTION) {
            return '';
        }
        return $collection . ':';
    }

}

Classes

Title Deprecated Summary
CachedStorage Defines the cached storage.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.