Skip to content

Instantly share code, notes, and snippets.

@damiankloip
Created October 1, 2014 12:57
Show Gist options
  • Save damiankloip/bf8f5309a17a30ec28f2 to your computer and use it in GitHub Desktop.
Save damiankloip/bf8f5309a17a30ec28f2 to your computer and use it in GitHub Desktop.
diff --git a/core/lib/Drupal/Core/Cache/CacheCollectorBackend.php b/core/lib/Drupal/Core/Cache/CacheCollectorBackend.php
new file mode 100644
index 0000000..b5b9b40
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/CacheCollectorBackend.php
@@ -0,0 +1,398 @@
+<?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,
+ );
+ }
+
+}
diff --git a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml
index b66568f..b744fa1 100644
--- a/core/modules/views/views.services.yml
+++ b/core/modules/views/views.services.yml
@@ -1,61 +1,66 @@
services:
+ cache.views_plugin_collector:
+ class: Drupal\Core\Cache\CacheCollectorBackend
+ arguments: [views_plugins, '@cache.discovery', '@lock']
+ tags:
+ - { name: needs_destruction }
plugin.manager.views.access:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [access, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [access, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.area:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [area, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [area, '@container.namespaces', '@views.views_data', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.argument:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [argument, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [argument, '@container.namespaces', '@views.views_data', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.argument_default:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [argument_default, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [argument_default, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.argument_validator:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [argument_validator, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [argument_validator, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.cache:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [cache, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [cache, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.display_extender:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [display_extender, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [display_extender, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.display:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [display, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [display, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.exposed_form:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [exposed_form, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [exposed_form, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.field:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [field, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [field, '@container.namespaces', '@views.views_data', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.filter:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [filter, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [filter, '@container.namespaces', '@views.views_data', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.join:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [join, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [join, '@container.namespaces', '@views.views_data', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.pager:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [pager, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [pager, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.query:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [query, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [query, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.relationship:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [relationship, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [relationship, '@container.namespaces', '@views.views_data', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.row:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [row, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [row, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.sort:
class: Drupal\views\Plugin\ViewsHandlerManager
- arguments: [sort, '@container.namespaces', '@views.views_data', '@cache.discovery', '@module_handler']
+ arguments: [sort, '@container.namespaces', '@views.views_data', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.style:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [style, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [style, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
plugin.manager.views.wizard:
class: Drupal\views\Plugin\ViewsPluginManager
- arguments: [wizard, '@container.namespaces', '@cache.discovery', '@module_handler']
+ arguments: [wizard, '@container.namespaces', '@cache.views_plugin_collector', '@module_handler']
views.views_data:
class: Drupal\views\ViewsData
arguments: ['@cache.discovery', '@config.factory', '@module_handler', '@language_manager']
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment