Skip to content

Instantly share code, notes, and snippets.

@Elendev
Created November 23, 2020 09:43
Show Gist options
  • Save Elendev/9625bb6216466520accd32d1d0aa25c4 to your computer and use it in GitHub Desktop.
Save Elendev/9625bb6216466520accd32d1d0aa25c4 to your computer and use it in GitHub Desktop.
KeyValueStores
<?php
namespace Drupal\sq_readonly\KeyValueStore;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\sq_readonly\ReadonlyHelper;
class KeyValueDecoratorFactory implements KeyValueFactoryInterface {
/**
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
*/
private $keyValueFactory;
/**
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface[]
*/
private $stores = [];
/**
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
private $cacheBackend;
public function __construct(KeyValueFactoryInterface $keyValueFactory, CacheBackendInterface $cacheBackend) {
$this->keyValueFactory = $keyValueFactory;
$this->cacheBackend = $cacheBackend;
}
/**
* @inheritDoc
*/
public function get($collection) {
if (!ReadonlyHelper::isReadonlyEnabled()) {
return $this->keyValueFactory->get($collection);
}
if (empty($this->stores[$collection])) {
$this->stores[$collection] = new KeyValueStoreLocalCacheDecorator($this->keyValueFactory->get($collection), $this->cacheBackend);
}
return $this->stores[$collection];
}
}
<?php
namespace Drupal\sq_readonly\KeyValueStore;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
use Drupal\sq_readonly\ReadonlyHelper;
class KeyValueExpirableDecoratorFactory implements KeyValueExpirableFactoryInterface {
/**
* @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
*/
private $keyValueFactory;
/**
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface[]
*/
private $stores = [];
/**
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
private $cacheBackend;
public function __construct(KeyValueFactoryInterface $keyValueFactory, CacheBackendInterface $cacheBackend) {
$this->keyValueFactory = $keyValueFactory;
$this->cacheBackend = $cacheBackend;
}
/**
* @inheritDoc
*/
public function get($collection) {
if (!ReadonlyHelper::isReadonlyEnabled()) {
return $this->keyValueFactory->get($collection);
}
if (empty($this->stores[$collection])) {
$this->stores[$collection] = new KeyValueStoreExpirableLocalCacheDecorator($this->keyValueFactory->get($collection), $this->cacheBackend);
}
return $this->stores[$collection];
}
}
<?php
namespace Drupal\sq_readonly\KeyValueStore;
use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
class KeyValueStoreExpirableLocalCacheDecorator extends KeyValueStoreLocalCacheDecorator implements KeyValueStoreExpirableInterface {
/**
* @inheritDoc
*/
public function setWithExpire($key, $value, $expire) {
$this->set($key, $value, $expire);
}
/**
* @inheritDoc
*/
public function setWithExpireIfNotExists($key, $value, $expire) {
$this->setIfNotExists($key, $value, $expire);
}
/**
* @inheritDoc
*/
public function setMultipleWithExpire(array $data, $expire) {
$this->setMultiple($data, $expire);
}
}
<?php
namespace Drupal\sq_readonly\KeyValueStore;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
class KeyValueStoreLocalCacheDecorator implements KeyValueStoreInterface {
const CACHE_ORIGINAL_KEYVALUE_KEY = 'original_keyvalue';
const CACHE_VALUE_KEY = 'value';
const CACHE_EMPTY_VALUE_KEY = 'empty_value';
/**
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
private $keyValueStore;
/**
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
private $cache;
public function __construct(KeyValueStoreInterface $keyValueStore, CacheBackendInterface $cache) {
$this->keyValueStore = $keyValueStore;
$this->cache = $cache;
}
/**
* @inheritDoc
*/
public function getCollectionName() {
return $this->keyValueStore->getCollectionName();
}
/**
* @inheritDoc
*/
public function has($key) {
$cacheKey = $this->getCacheKey($key);
$cachedValue = $this->cache->get($cacheKey);
// No valid local override
if (!$this->isCacheKeyUpToDate($key) || empty($cachedValue)) {
return $this->keyValueStore->has($key);
}
return !$cachedValue->data[self::CACHE_EMPTY_VALUE_KEY];
}
/**
* @inheritDoc
*/
public function get($key, $default = NULL) {
$cacheKey = $this->getCacheKey($key);
$cachedValue = $this->cache->get($cacheKey);
if (!$this->isCacheKeyUpToDate($key) || empty($cachedValue)) {
return $this->keyValueStore->get($key, $default);
}
return $cachedValue->data[self::CACHE_VALUE_KEY];
}
/**
* @inheritDoc
*/
public function getMultiple(array $keys) {
$values = $this->keyValueStore->getMultiple($keys);
foreach ($values as $key => $value) {
$cacheKey = $this->getCacheKey($key);
$cachedValue = $this->cache->get($cacheKey);
if ($this->isCacheKeyUpToDate($key) && !empty($cachedValue)) {
$values[$key] = $cachedValue->data[self::CACHE_VALUE_KEY];
}
}
return $values;
}
/**
* @inheritDoc
*/
public function getAll() {
$values = $this->keyValueStore->getAll();
foreach ($values as $key => $value) {
$cacheKey = $this->getCacheKey($key);
$cachedValue = $this->cache->get($cacheKey);
if ($this->isCacheKeyUpToDate($key) && !empty($cachedValue)) {
$values[$key] = $cachedValue->data[self::CACHE_VALUE_KEY];
}
}
return $values;
}
/**
* @inheritDoc
*/
public function set($key, $value, $expire = Cache::PERMANENT) {
$this->cache->set($this->getCacheKey($key), [
self::CACHE_ORIGINAL_KEYVALUE_KEY => $this->keyValueStore->get($key),
self::CACHE_VALUE_KEY => $value,
self::CACHE_EMPTY_VALUE_KEY => FALSE,
], $expire);
}
/**
* @inheritDoc
*/
public function setIfNotExists($key, $value, $expire = Cache::PERMANENT) {
if (!$this->has($key)) {
$this->set($key, $value, $expire);
}
}
/**
* @inheritDoc
*/
public function setMultiple(array $data, $expire = Cache::PERMANENT) {
/*
* It's not possible to take advantage of multiple insert in the cache here
* because of the expire option that is not supported in the multiple-insert
* option of the cache.
*/
foreach ($data as $key => $value) {
$this->set($this->getCacheKey($key), $value, $expire);
}
}
/**
* @inheritDoc
*/
public function rename($key, $new_key) {
/*
* Set the previous value as empty and add a new value
*/
$value = $this->get($key);
$this->delete($key);
$this->set($new_key, $value);
}
/**
* @inheritDoc
*/
public function delete($key) {
$cacheKey = $this->getCacheKey($key);
if (!$this->keyValueStore->has($key)) {
// Re-use the already non-existent value of the keyValueStore
$this->cache->delete($cacheKey);
} else {
// Replace the existent keyValueStore value by a non-existent value in the cache
$this->cache->set($cacheKey, [
self::CACHE_ORIGINAL_KEYVALUE_KEY => $this->keyValueStore->get($key),
self::CACHE_VALUE_KEY => NULL,
self::CACHE_EMPTY_VALUE_KEY => TRUE,
]);
}
}
/**
* @inheritDoc
*/
public function deleteMultiple(array $keys) {
foreach ($keys as $key) {
$this->delete($key);
}
}
/**
* @inheritDoc
*/
public function deleteAll() {
$values = $this->getAll();
foreach ($values as $key => $value) {
$this->delete($key);
}
}
/**
* The response of this function doesn't tell if the cache is set or not, only if we can rely on it (cache empty => use the keystore,
* cache full => use the value of the cache).
* @param $key
*
* @return bool true if the cache is up to date.
*/
private function isCacheKeyUpToDate($key) {
$cacheKey = $this->getCacheKey($key);
$cachedValue = $this->cache->get($cacheKey);
if (!empty($cachedValue)) {
// Special case: the cache is badly formatted, we return false
if (
!array_key_exists(self::CACHE_ORIGINAL_KEYVALUE_KEY, $cachedValue->data)
|| !array_key_exists(self::CACHE_VALUE_KEY, $cachedValue->data)
|| !array_key_exists(self::CACHE_EMPTY_VALUE_KEY, $cachedValue->data)
) {
return false;
}
return $cachedValue->data[self::CACHE_ORIGINAL_KEYVALUE_KEY] === $this->keyValueStore->get($key);
}
return true;
}
/**
* Generate a cache key based on the given key and the collection name.
* @param $key
*
* @return string
*/
private function getCacheKey($key) {
return 'KeyValueStoreDecorator:' . $this->getCollectionName() . ':' . $key;
}
}
@acondura
Copy link

Great code! I'm trying to use it myself and test it but I can seem to find the ReadonlyHelper class.
Can you share it please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment