Created
October 1, 2014 12:57
-
-
Save damiankloip/bf8f5309a17a30ec28f2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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