class CacheCollector
Same name in other branches
- 9 core/lib/Drupal/Core/Cache/CacheCollector.php \Drupal\Core\Cache\CacheCollector
- 8.9.x core/lib/Drupal/Core/Cache/CacheCollector.php \Drupal\Core\Cache\CacheCollector
- 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
- class \Drupal\Core\Cache\CacheCollector implements \Drupal\Core\Cache\CacheCollectorInterface, \Drupal\Core\DestructableInterface
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
File
-
core/
lib/ Drupal/ Core/ Cache/ CacheCollector.php, line 25
Namespace
Drupal\Core\CacheView 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.