diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index 3e3853a..84c2cf8 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -2559,7 +2559,7 @@ function module_hook($module, $hook) { * @return Drupal\Core\KeyValueStore\KeyValueStoreInterface */ function state() { - return drupal_container()->get('keyvalue')->get('state'); + return drupal_container()->get('state'); } /** diff --git a/core/lib/Drupal/Core/Cache/CacheCollector.php b/core/lib/Drupal/Core/Cache/CacheCollector.php new file mode 100644 index 0000000..bbfd77e --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheCollector.php @@ -0,0 +1,217 @@ +has() needs to correctly return (equivalent to + * array_key_exists() vs. isset()). This should not be necessary in the majority + * of cases. + * + * Classes extending this class must override at least the + * CacheCollector::resolveCacheMiss() method to have a working implementation. + */ +abstract class CacheCollector implements CacheCollectorInterface { + + /** + * A cid to pass to cache()->set() and cache()->get(). + * + * @var string + */ + protected $cid; + + /** + * A tags array to pass to cache()->set(). + * + * @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(); + + /** + * Constructs a CacheArray object. + * + * @param string $cid + * The cid for the array being cached. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache + * The cache backend. + * @param array $tags + * (optional) The tags to specify for the cache item. + */ + public function __construct($cid, CacheBackendInterface $cache, \Drupal\Core\Lock\LockBackendInterface $lock, $tags = array()) { + $this->cid = $cid; + $this->cache = $cache; + $this->tags = $tags; + $this->lock = $lock; + + if ($cached = $this->cache->get($this->cid)) { + $this->storage = $cached->data; + } + } + + /** + * Implements CacheCollectorInterface::has(). + */ + public function has($key) { + return $this->get($key) !== NULL; + } + + /** + * Implements CacheCollectorInterface::get(). + */ + public function get($key) { + if (isset($this->storage[$key]) || array_key_exists($key, $this->storage)) { + return $this->storage[$key]; + } + else { + return $this->resolveCacheMiss($key); + } + } + + /** + * Implements 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->storage[$key] = $value; + // The key might have been marked for deletion. + unset($this->keysToRemove[$key]); + + } + + /** + * Implements CacheCollectorInterface::delete(). + */ + public function delete($key) { + unset($this->storage[$key]); + $this->keysToRemove[$key] = $key; + // The key might have been marked for persisting. + unset($this->keysToPersist[$key]); + } + + /** + * Flags an offset value to be written to the persistent cache. + * + * @param $key + * The key that was request. + * @param $persist + * Optional boolean to specify 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 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 implementatio to look up the + * actual value and allow it to be cached. + * + * @param $key + * The offset that was requested. + * + * @return + * 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 $data + * The data to write to the persistent cache. + * @param $lock + * Whether to acquire a lock before writing to cache. + */ + protected function updateCache($lock = TRUE) { + $data = array(); + foreach ($this->keysToPersist as $offset => $persist) { + if ($persist) { + $data[$offset] = $this->storage[$offset]; + } + } + if (empty($data)) { + return; + } + + // Lock cache writes to help avoid stampedes. + // To implement locking for cache misses, override __construct(). + $lock_name = $this->cid . ':' . __CLASS__; + if (!$lock || $this->lock->acquire($lock_name)) { + if ($cached = $this->cache->get($this->cid)) { + $data = array_merge($cached->data, $data); + } + // Remove keys marked for deletion. + foreach ($this->keysToRemove as $delete_key) { + unset($data[$delete_key]); + } + $this->cache->set($this->cid, $data, CacheBackendInterface::CACHE_PERMANENT, $this->tags); + if ($lock) { + $this->lock->release($lock_name); + } + } + } + + /** + * Implemnts KernelServiceTerminator::terminate(). + */ + public function terminate() { + $this->updateCache(); + } +} diff --git a/core/lib/Drupal/Core/Cache/CacheCollectorInterface.php b/core/lib/Drupal/Core/Cache/CacheCollectorInterface.php new file mode 100644 index 0000000..ee67c41 --- /dev/null +++ b/core/lib/Drupal/Core/Cache/CacheCollectorInterface.php @@ -0,0 +1,66 @@ +addArgument('config'); $container + ->register('cache.cache', 'Drupal\Core\Cache\CacheBackendInterface') + ->setFactoryClass('Drupal\Core\Cache\CacheFactory') + ->setFactoryMethod('get') + ->addArgument('cache'); + + $container ->register('config.storage', 'Drupal\Core\Config\CachedStorage') ->addArgument(new Reference('config.cachedstorage.storage')) ->addArgument(new Reference('cache.config')); @@ -83,9 +89,10 @@ public function build(ContainerBuilder $container) { ->setFactoryMethod('getSingleton'); // Register the State k/v store as a service. - $container->register('state', 'Drupal\Core\KeyValueStore\KeyValueStoreInterface') - ->setFactoryService(new Reference('keyvalue')) - ->setFactoryMethod('get') + $container->register('state', 'Drupal\Core\KeyValueStore\KeyValueCacheDecorator') + ->addArgument(new Reference('cache.cache')) + ->addArgument(new Reference('lock')) + ->addArgument(new Reference('keyvalue')) ->addArgument('state'); // Register the Queue factory. diff --git a/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php b/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php new file mode 100644 index 0000000..2c09d7b --- /dev/null +++ b/core/lib/Drupal/Core/KeyValueStore/KeyValueCacheDecorator.php @@ -0,0 +1,93 @@ +keyValueStore = $keyValueFactory->get($collection); + } + + protected function resolveCacheMiss($offset) { + $this->storage[$offset] = $this->keyValueStore->get($offset); + $this->persist($offset); + return $this->storage[$offset]; + } + + public function delete($key) { + parent::delete($key); + $this->keyValueStore->delete($key); + } + + public function deleteMultiple(array $keys) { + foreach ($keys as $key) { + $this->delete($key); + } + $this->keyValueStore->deleteMultiple($keys); + } + + public function getAll() { + // Don't cache this. + return $this->keyValueStore->getAll(); + } + + public function getCollectionName() { + return $this->keyValueStore->getCollectionName(); + } + + public function getMultiple(array $keys) { + $values = array(); + foreach ($keys as $key) { + $values[$key] = $this->get($key); + } + return $values; + } + + public function setIfNotExists($key, $value) { + if ($this->keyValueStore->setIfNotExists($key, $value)) { + $this->set($key, $value); + } + } + + public function setMultiple(array $data) { + $this->keyValueStore->setMultiple($data); + foreach ($data as $key => $value) { + parent::set($key, $value); + $this->persist($key); + } + } + + public function set($key, $value) { + $this->keyValueStore->set($key, $value); + parent::set($key, $value); + $this->persist($key); + } + + /** + * @todo: Remove this once the service terminator can be used. + */ + public function __destruct() { + $this->terminate(); + } + +}