class CacheCollector

Same name in other branches
  1. 9 core/lib/Drupal/Core/Cache/CacheCollector.php \Drupal\Core\Cache\CacheCollector
  2. 8.9.x core/lib/Drupal/Core/Cache/CacheCollector.php \Drupal\Core\Cache\CacheCollector
  3. 11.x core/lib/Drupal/Core/Cache/CacheCollector.php \Drupal\Core\Cache\CacheCollector

Default implementation for CacheCollectorInterface.

By default, the class accounts for caches where calling functions might request keys that won't exist even after a cache rebuild. This prevents situations where a cache rebuild would be triggered over and over due to a 'missing' item. These cases are stored internally as a value of NULL. This means that the CacheCollector::get() method must be overridden if caching data where the values can legitimately be NULL, and where CacheCollector->has() needs to correctly return (equivalent to array_key_exists() vs. isset()). This should not be necessary in the majority of cases.

Hierarchy

Expanded class hierarchy of CacheCollector

Related topics

8 files declare their use of CacheCollector
AliasWhitelist.php in core/modules/path_alias/src/AliasWhitelist.php
BlockContentUuidLookup.php in core/modules/block_content/src/BlockContentUuidLookup.php
CacheCollectorHelper.php in core/tests/Drupal/Tests/Core/Cache/CacheCollectorHelper.php
LibraryDiscoveryCollector.php in core/lib/Drupal/Core/Asset/LibraryDiscoveryCollector.php
LocaleLookup.php in core/modules/locale/src/LocaleLookup.php

... See full list

File

core/lib/Drupal/Core/Cache/CacheCollector.php, line 25

Namespace

Drupal\Core\Cache
View source
abstract class CacheCollector implements CacheCollectorInterface, DestructableInterface {
    
    /**
     * The cache id that is used for the cache entry.
     *
     * @var string
     */
    protected $cid;
    
    /**
     * A list of tags that are used for the cache entry.
     *
     * @var array
     */
    protected $tags;
    
    /**
     * The cache backend that should be used.
     *
     * @var \Drupal\Core\Cache\CacheBackendInterface
     */
    protected $cache;
    
    /**
     * The lock backend that should be used.
     *
     * @var \Drupal\Core\Lock\LockBackendInterface
     */
    protected $lock;
    
    /**
     * An array of keys to add to the cache on service termination.
     *
     * @var array
     */
    protected $keysToPersist = [];
    
    /**
     * An array of keys to remove from the cache on service termination.
     *
     * @var array
     */
    protected $keysToRemove = [];
    
    /**
     * Storage for the data itself.
     *
     * @var array
     */
    protected $storage = [];
    
    /**
     * Stores the cache creation time.
     *
     * This is used to check if an invalidated cache item has been overwritten in
     * the meantime.
     *
     * @var int
     */
    protected $cacheCreated;
    
    /**
     * Flag that indicates of the cache has been invalidated.
     *
     * @var bool
     */
    protected $cacheInvalidated = FALSE;
    
    /**
     * Indicates if the collected cache was already loaded.
     *
     * The collected cache is lazy loaded when an entry is set, get or deleted.
     *
     * @var bool
     */
    protected $cacheLoaded = FALSE;
    
    /**
     * Constructs a CacheCollector object.
     *
     * @param string $cid
     *   The cid for the array being cached.
     * @param \Drupal\Core\Cache\CacheBackendInterface $cache
     *   The cache backend.
     * @param \Drupal\Core\Lock\LockBackendInterface $lock
     *   The lock backend.
     * @param array $tags
     *   (optional) The tags to specify for the cache item.
     */
    public function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, array $tags = []) {
        assert(Inspector::assertAllStrings($tags), 'Cache tags must be strings.');
        $this->cid = $cid;
        $this->cache = $cache;
        $this->tags = $tags;
        $this->lock = $lock;
    }
    
    /**
     * Gets the cache ID.
     *
     * @return string
     */
    protected function getCid() {
        return $this->cid;
    }
    
    /**
     * {@inheritdoc}
     */
    public function has($key) {
        // Make sure the value is loaded.
        $this->get($key);
        return \array_key_exists($key, $this->storage);
    }
    
    /**
     * {@inheritdoc}
     */
    public function get($key) {
        $this->lazyLoadCache();
        if (\array_key_exists($key, $this->storage)) {
            return $this->storage[$key];
        }
        else {
            return $this->resolveCacheMiss($key);
        }
    }
    
    /**
     * Implements \Drupal\Core\Cache\CacheCollectorInterface::set().
     *
     * This is not persisted by default. In practice this means that setting a
     * value will only apply while the object is in scope and will not be written
     * back to the persistent cache. This follows a similar pattern to static vs.
     * persistent caching in procedural code. Extending classes may wish to alter
     * this behavior, for example by adding a call to persist(). If you are
     * writing data to somewhere in addition to the cache item in ::set(), you
     * should call static::updateCache() at the end of your ::set implementation.
     * This avoids a race condition if another request starts with an empty cache
     * before your ::set() call. For example: Drupal\Core\State\State.
     */
    public function set($key, $value) {
        $this->lazyLoadCache();
        $this->storage[$key] = $value;
        // The key might have been marked for deletion.
        unset($this->keysToRemove[$key]);
        $this->invalidateCache();
    }
    
    /**
     * {@inheritdoc}
     */
    public function delete($key) {
        $this->lazyLoadCache();
        unset($this->storage[$key]);
        $this->keysToRemove[$key] = $key;
        // The key might have been marked for persisting.
        unset($this->keysToPersist[$key]);
        $this->invalidateCache();
    }
    
    /**
     * Flags an offset value to be written to the persistent cache.
     *
     * @param string $key
     *   The key that was requested.
     * @param bool $persist
     *   (optional) Whether the offset should be persisted or not, defaults to
     *   TRUE. When called with $persist = FALSE the offset will be un-flagged so
     *   that it will not be written at the end of the request.
     */
    protected function persist($key, $persist = TRUE) {
        $this->keysToPersist[$key] = $persist;
    }
    
    /**
     * Resolves a cache miss.
     *
     * When an offset is not found in the object, this is treated as a cache
     * miss. This method allows classes using this implementation to look up the
     * actual value and allow it to be cached.
     *
     * @param string $key
     *   The offset that was requested.
     *
     * @return mixed
     *   The value of the offset, or NULL if no value was found.
     */
    protected abstract function resolveCacheMiss($key);
    
    /**
     * Writes a value to the persistent cache immediately.
     *
     * @param bool $lock
     *   (optional) Whether to acquire a lock before writing to cache. Defaults to
     *   TRUE.
     */
    protected function updateCache($lock = TRUE) {
        $data = [];
        foreach ($this->keysToPersist as $offset => $persist) {
            if ($persist) {
                $data[$offset] = $this->storage[$offset];
            }
        }
        if (empty($data) && empty($this->keysToRemove)) {
            return;
        }
        // Lock cache writes to help avoid stampedes.
        $cid = $this->getCid();
        $lock_name = $cid . ':' . __CLASS__;
        if (!$lock || $this->lock
            ->acquire($lock_name)) {
            // Set and delete operations invalidate the cache item. Try to also load
            // an eventually invalidated cache entry, only update an invalidated cache
            // entry if the creation date did not change as this could result in an
            // inconsistent cache.
            if ($cache = $this->cache
                ->get($cid, $this->cacheInvalidated)) {
                if ($this->cacheInvalidated && $cache->created != $this->cacheCreated) {
                    // We have invalidated the cache in this request and got a different
                    // cache entry. Do not attempt to overwrite data that might have been
                    // changed in a different request. We'll let the cache rebuild in
                    // later requests.
                    $this->cache
                        ->delete($cid);
                    $this->lock
                        ->release($lock_name);
                    return;
                }
                // If there wasn't a cache item at the beginning of the request, but
                // there is now, then there has been a cache write in the interim.
                // Discard our data if so since the cache may have been written by
                // a request that was also setting data.
                if (!$this->cacheCreated) {
                    return;
                }
                $data = array_merge($cache->data, $data);
            }
            elseif ($this->cacheCreated) {
                // Getting here indicates that there was a cache entry at the
                // beginning of the request, but now it's gone (some other process
                // must have cleared it). We back out to prevent corrupting the cache
                // with incomplete data, since we won't be able to properly merge
                // the existing cache data from earlier with the new data.
                // A future request will properly hydrate the cache from scratch.
                if ($lock) {
                    $this->lock
                        ->release($lock_name);
                }
                return;
            }
            // Remove keys marked for deletion.
            foreach ($this->keysToRemove as $delete_key) {
                unset($data[$delete_key]);
            }
            $this->cache
                ->set($cid, $data, Cache::PERMANENT, $this->tags);
            if ($lock) {
                $this->lock
                    ->release($lock_name);
            }
        }
        $this->keysToPersist = [];
        $this->keysToRemove = [];
    }
    
    /**
     * Normalizes a cache ID in order to comply with database limitations.
     *
     * @param string $cid
     *   The passed in cache ID.
     *
     * @return string
     *   An ASCII-encoded cache ID that is at most 255 characters long.
     */
    protected function normalizeLockName($cid) {
        @trigger_error(sprintf('%s is deprecated in drupal:10.3.0 and is removed from drupal:11.0.0. The lock service is responsible for normalizing the lock name. See https://www.drupal.org/node/3436961', __METHOD__), E_USER_DEPRECATED);
        // Nothing to do if the ID is a US ASCII string of 255 characters or less.
        $cid_is_ascii = mb_check_encoding($cid, 'ASCII');
        if (strlen($cid) <= 255 && $cid_is_ascii) {
            return $cid;
        }
        // Return a string that uses as much as possible of the original cache ID
        // with the hash appended.
        $hash = Crypt::hashBase64($cid);
        if (!$cid_is_ascii) {
            return $hash;
        }
        return substr($cid, 0, 255 - strlen($hash)) . $hash;
    }
    
    /**
     * {@inheritdoc}
     */
    public function reset() {
        $this->storage = [];
        $this->keysToPersist = [];
        $this->keysToRemove = [];
        $this->cacheLoaded = FALSE;
    }
    
    /**
     * {@inheritdoc}
     */
    public function clear() {
        $this->reset();
        if ($this->tags) {
            Cache::invalidateTags($this->tags);
        }
        else {
            $this->cache
                ->delete($this->getCid());
        }
    }
    
    /**
     * {@inheritdoc}
     */
    public function destruct() {
        $this->updateCache();
    }
    
    /**
     * Loads the cache if not already done.
     */
    protected function lazyLoadCache() {
        if ($this->cacheLoaded) {
            return;
        }
        // The cache was not yet loaded, set flag to TRUE.
        $this->cacheLoaded = TRUE;
        if ($cache = $this->cache
            ->get($this->getCid())) {
            $this->cacheCreated = $cache->created;
            $this->storage = $cache->data;
        }
    }
    
    /**
     * Invalidate the cache.
     */
    protected function invalidateCache() {
        // Invalidate the cache to make sure that other requests immediately see the
        // deletion before this request is terminated.
        $this->cache
            ->invalidate($this->getCid());
        $this->cacheInvalidated = TRUE;
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
CacheCollector::$cache protected property The cache backend that should be used. 1
CacheCollector::$cacheCreated protected property Stores the cache creation time.
CacheCollector::$cacheInvalidated protected property Flag that indicates of the cache has been invalidated.
CacheCollector::$cacheLoaded protected property Indicates if the collected cache was already loaded.
CacheCollector::$cid protected property The cache id that is used for the cache entry.
CacheCollector::$keysToPersist protected property An array of keys to add to the cache on service termination.
CacheCollector::$keysToRemove protected property An array of keys to remove from the cache on service termination.
CacheCollector::$lock protected property The lock backend that should be used. 1
CacheCollector::$storage protected property Storage for the data itself.
CacheCollector::$tags protected property A list of tags that are used for the cache entry.
CacheCollector::clear public function Overrides CacheCollectorInterface::clear 1
CacheCollector::delete public function Overrides CacheCollectorInterface::delete 1
CacheCollector::destruct public function Overrides DestructableInterface::destruct
CacheCollector::get public function Overrides CacheCollectorInterface::get 3
CacheCollector::getCid protected function Gets the cache ID. 3
CacheCollector::has public function Overrides CacheCollectorInterface::has 1
CacheCollector::invalidateCache protected function Invalidate the cache. 1
CacheCollector::lazyLoadCache protected function Loads the cache if not already done. 1
CacheCollector::normalizeLockName protected function Normalizes a cache ID in order to comply with database limitations. 1
CacheCollector::persist protected function Flags an offset value to be written to the persistent cache.
CacheCollector::reset public function Overrides CacheCollectorInterface::reset 1
CacheCollector::resolveCacheMiss abstract protected function Resolves a cache miss. 8
CacheCollector::set public function Implements \Drupal\Core\Cache\CacheCollectorInterface::set(). Overrides CacheCollectorInterface::set 2
CacheCollector::updateCache protected function Writes a value to the persistent cache immediately. 2
CacheCollector::__construct public function Constructs a CacheCollector object. 7

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