Skip to content

Instantly share code, notes, and snippets.

@damiankloip
Created September 30, 2014 12:48
Show Gist options
  • Save damiankloip/80c142e17d6a05c8a123 to your computer and use it in GitHub Desktop.
Save damiankloip/80c142e17d6a05c8a123 to your computer and use it in GitHub Desktop.
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheCollectorBackend.
*/
namespace Drupal\Core\Cache;
use Drupal\Core\DestructableInterface;
use Drupal\Core\Lock\LockBackendInterface;
/**
* Class CacheCollectorBackend
*/
class CacheCollectorBackend implements CacheBackendInterface, DestructableInterface {
/**
* The cache backend.
*
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cacheBackend;
/**
* The lock backend.
*
* @var \Drupal\Core\Lock\LockBackendInterface
*/
protected $lock;
/**
* 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;
/**
* 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 CacheCollectorBackend.
*/
public function __construct($cid, CacheBackendInterface $cache_backend, LockBackendInterface $lock, array $default_tags = array()) {
$this->cid = $cid;
$this->cacheBackend = $cache_backend;
$this->lock = $lock;
$this->tags = $default_tags;
}
/**
* {@inheritdoc}
*/
public function get($cid, $allow_invalid = FALSE) {
$this->lazyLoadCache();
if (isset($this->storage[$cid]) || array_key_exists($cid, $this->storage)) {
return $this->createCacheObject($this->storage[$cid]);
}
}
/**
* {@inheritdoc}
*/
public function getMultiple(&$cids, $allow_invalid = FALSE) {
$cache = array();
foreach ($cids as $cid) {
$cache[$cid] = $this->get($cid, $allow_invalid);
}
$cids = array_diff($cids, array_keys($cache));
return $cache;
}
/**
* {@inheritdoc}
*/
public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {
$item = array(
'data' => $data,
'expire' => $expire,
'tags' => $tags,
);
$this->setMultiple(array($cid => $item));
}
/**
* {@inheritdoc}
*/
public function setMultiple(array $items) {
$this->lazyLoadCache();
$deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array());
$invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
foreach ($items as $cid => $item) {
$item += array(
// @todo Ignore this for now.
'expire' => CacheBackendInterface::CACHE_PERMANENT,
'tags' => array(),
);
Cache::mergeTags($this->tags, array_unique($item['tags']));
foreach ($item['tags'] as $tag) {
if (isset($deleted_tags[$tag])) {
unset($deleted_tags[$tag]);
}
if (isset($invalidated_tags[$tag])) {
unset($invalidated_tags[$tag]);
}
}
$this->storage[$cid] = $item['data'];
$this->persist($cid);
// The key might have been marked for deletion.
unset($this->keysToRemove[$cid]);
}
$this->invalidateCache();
}
/**
* {@inheritdoc}
*/
public function delete($cid) {
$this->lazyLoadCache();
unset($this->storage[$cid]);
$this->keysToRemove[$cid] = $cid;
// The key might have been marked for persisting.
unset($this->keysToPersist[$cid]);
$this->invalidateCache();
}
/**
* {@inheritdoc}
*/
public function deleteMultiple(array $cids) {
$this->lazyLoadCache();
foreach ($cids as $cid) {
unset($this->storage[$cid]);
$this->keysToRemove[$cid] = $cid;
// The key might have been marked for persisting.
unset($this->keysToPersist[$cid]);
}
$this->invalidateCache();
}
/**
* {@inheritdoc}
*/
public function deleteAll() {
$this->cacheBackend->delete($this->getCid());
$this->reset();
}
/**
* {@inheritdoc}
*/
public function deleteTags(array $tags) {
$this->cacheBackend->deleteTags($tags);
$this->reset();
}
/**
* {@inheritdoc}
*/
public function invalidate($cid) {
$this->cacheBackend->invalidate($this->getCid());
$this->reset();
}
/**
* {@inheritdoc}
*/
public function invalidateAll() {
$this->cacheBackend->invalidate($this->getCid());
$this->reset();
}
/**
* {@inheritdoc}
*/
public function invalidateMultiple(array $cids) {
$this->cacheBackend->invalidate($this->getCid());
}
/**
* Performs garbage collection on a cache bin.
*
* The backend may choose to delete expired or invalidated items.
*/
public function garbageCollection() {
$this->cacheBackend->garbageCollection();
}
/**
* {@inheritdoc}
*/
public function invalidateTags(array $tags) {
$this->cacheBackend->invalidateTags($tags);
$this->reset();
}
/**
* {@inheritdoc}
*/
public function removeBin() {
$this->cacheBackend->delete($this->getCid());
$this->reset();
}
/**
* {@inheritdoc}
*/
public function destruct() {
$this->updateCache();
}
/**
* {@inheritdoc}
*/
protected function reset() {
$this->storage = array();
$this->keysToPersist = array();
$this->keysToRemove = array();
$this->cacheLoaded = FALSE;
}
/**
* 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->cacheBackend->get($this->getCid())) {
$this->cacheCreated = $cache->created;
$this->storage = $cache->data;
$this->tags = $cache->tags;
}
}
/**
* 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;
}
/**
* 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 = $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->cacheBackend->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->cacheBackend->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->cacheBackend->set($cid, $data, Cache::PERMANENT, $this->tags);
if ($lock) {
$this->lock->release($lock_name);
}
}
$this->keysToPersist = array();
$this->keysToRemove = array();
}
/**
* 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->cacheBackend->invalidate($this->getCid());
$this->cacheInvalidated = TRUE;
}
/**
* Gets the cache ID.
*
* @return string
*/
protected function getCid() {
return $this->cid;
}
/**
*
*/
protected function createCacheObject($data) {
return (object) array(
'data' => $data,
'created' => $this->cacheCreated,
'expire' => Cache::PERMANENT,
'tags' => $this->tags,
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment