Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
92.86% |
13 / 14 |
CRAP | |
94.87% |
74 / 78 |
| CacheCollector | |
0.00% |
0 / 1 |
|
93.33% |
14 / 15 |
35.17 | |
94.87% |
74 / 78 |
| __construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
6 / 6 |
|||
| getCid | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| has | |
100.00% |
1 / 1 |
2 | |
100.00% |
2 / 2 |
|||
| get | |
100.00% |
1 / 1 |
3 | |
100.00% |
4 / 4 |
|||
| set | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
| delete | |
100.00% |
1 / 1 |
1 | |
100.00% |
6 / 6 |
|||
| persist | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| resolveCacheMiss | |
100.00% |
1 / 1 |
1 | |
100.00% |
0 / 0 |
|||
| updateCache | |
100.00% |
1 / 1 |
12 | |
100.00% |
23 / 23 |
|||
| normalizeLockName | |
0.00% |
0 / 1 |
6.99 | |
42.86% |
3 / 7 |
|||
| reset | |
100.00% |
1 / 1 |
1 | |
100.00% |
5 / 5 |
|||
| clear | |
100.00% |
1 / 1 |
2 | |
100.00% |
5 / 5 |
|||
| destruct | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
| lazyLoadCache | |
100.00% |
1 / 1 |
3 | |
100.00% |
7 / 7 |
|||
| invalidateCache | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Cache\CacheCollector. | |
| */ | |
| namespace Drupal\Core\Cache; | |
| use Drupal\Component\Utility\Crypt; | |
| use Drupal\Core\DestructableInterface; | |
| use Drupal\Core\Lock\LockBackendInterface; | |
| /** | |
| * 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. | |
| * | |
| * @ingroup cache | |
| */ | |
| 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 = array(); | |
| /** | |
| * An array of keys to remove from the cache on service termination. | |
| * | |
| * @var array | |
| */ | |
| protected $keysToRemove = array(); | |
| /** | |
| * Storage for the data itself. | |
| * | |
| * @var array | |
| */ | |
| protected $storage = array(); | |
| /** | |
| * 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 = array()) { | |
| assert('\Drupal\Component\Assertion\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 isset($this->storage[$key]) || array_key_exists($key, $this->storage); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function get($key) { | |
| $this->lazyLoadCache(); | |
| if (isset($this->storage[$key]) || 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(). | |
| */ | |
| 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 unflagged 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. | |
| */ | |
| abstract protected 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 = array(); | |
| 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 = $this->normalizeLockName($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; | |
| } | |
| $data = array_merge($cache->data, $data); | |
| } | |
| // 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 = array(); | |
| $this->keysToRemove = array(); | |
| } | |
| /** | |
| * 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) { | |
| // 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 = array(); | |
| $this->keysToPersist = array(); | |
| $this->keysToRemove = array(); | |
| $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; | |
| } | |
| } |