From 6dbf704a1356562744808639d325707b46b485d9 Mon Sep 17 00:00:00 2001 From: Matias Griese Date: Mon, 29 May 2017 10:12:02 +0300 Subject: [PATCH] Implement memory and chain cache --- .../Framework/Cache/Adapter/ChainCache.php | 177 ++++++++++++++++++ .../Framework/Cache/Adapter/DoctrineCache.php | 18 +- .../Framework/Cache/Adapter/MemoryCache.php | 26 ++- .../Grav/Framework/Cache/CacheInterface.php | 8 + system/src/Grav/Framework/Cache/CacheMiss.php | 20 ++ .../src/Grav/Framework/Cache/CacheTrait.php | 118 +++++++----- 6 files changed, 302 insertions(+), 65 deletions(-) create mode 100644 system/src/Grav/Framework/Cache/Adapter/ChainCache.php create mode 100644 system/src/Grav/Framework/Cache/CacheMiss.php diff --git a/system/src/Grav/Framework/Cache/Adapter/ChainCache.php b/system/src/Grav/Framework/Cache/Adapter/ChainCache.php new file mode 100644 index 000000000..1402fbaa5 --- /dev/null +++ b/system/src/Grav/Framework/Cache/Adapter/ChainCache.php @@ -0,0 +1,177 @@ +caches = array_values($caches); + $this->count = count($caches); + $this->miss = new \stdClass; + } + + public function doGet($key) + { + foreach ($this->caches as $i => $cache) { + $value = $cache->doGet($key); + if ($this->isHit($value)) { + while (--$i >= 0) { + // Update all the previous caches with missing value. + $this->caches[$i]->doSet($key, $value, $this->getDefaultLifetime()); + } + + return $value; + } + } + + return $this->miss(); + } + + public function doSet($key, $value, $ttl) + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doSet($key, $value, $ttl) && $success; + } + + return $success; + } + + public function doDelete($key) + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doDelete($key) && $success; + } + + return $success; + } + + public function doClear() + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doClear() && $success; + } + return $success; + } + + + public function doGetMultiple($keys) + { + $found = []; + $missing = []; + foreach ($this->caches as $i => $cache) { + $values = $cache->doGetMultiple($i ? $missing[$i - 1] : $keys); + + foreach ($values as $key => $value) { + if ($this->isHit($value)) { + $found[$key] = $value; + } else { + $missing[$i][$key] = true; + } + } + + if (empty($missing[$i])) { + break; + } + } + + $values = []; + // Update all the previous caches with missing values. + foreach (array_reverse($missing) as $i => $keys) { + $values += array_intersect($found, $keys); + $this->caches[$i]->doSetMultiple($values, $this->getDefaultLifetime()); + } + + return $found; + } + + public function doSetMultiple($values, $ttl) + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doSetMultiple($values, $ttl) && $success; + } + + return $success; + } + + public function doDeleteMultiple($keys) + { + $success = true; + $i = $this->count; + + while ($i--) { + $success = $this->caches[$i]->doDeleteMultiple($keys) && $success; + } + + return $success; + } + + public function doHas($key) + { + foreach ($this->caches as $cache) { + if ($cache->doHas($key)) { + return true; + } + } + + return false; + } +} diff --git a/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php b/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php index 57b127391..7499aba04 100644 --- a/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php +++ b/system/src/Grav/Framework/Cache/Adapter/DoctrineCache.php @@ -41,40 +41,40 @@ class DoctrineCache extends AbstractCache $this->driver = $doctrineCache; } - protected function doGet($key, $default) + public function doGet($key) { $value = $this->driver->fetch($key); // Doctrine cache does not differentiate between no result and cached 'false'. Make sure that we do. - return $value !== false || $this->driver->contains($key) ? $value : $default; + return $value !== false || $this->driver->contains($key) ? $value : $this->miss(); } - protected function doSet($key, $value, $ttl) + public function doSet($key, $value, $ttl) { return $this->driver->save($key, $value, (int) $ttl); } - protected function doDelete($key) + public function doDelete($key) { return $this->driver->delete($key); } - protected function doClear() + public function doClear() { return $this->driver->deleteAll(); } - protected function doGetMultiple($keys, $default) + public function doGetMultiple($keys) { return $this->driver->fetchMultiple($keys); } - protected function doSetMultiple($values, $ttl) + public function doSetMultiple($values, $ttl) { return $this->driver->saveMultiple($values, (int) $ttl); } - protected function doDeleteMultiple($keys) + public function doDeleteMultiple($keys) { // TODO: Remove when Doctrine Cache has been updated to support the feature. if (!method_exists($this->driver, 'deleteMultiple')) { @@ -89,7 +89,7 @@ class DoctrineCache extends AbstractCache return $this->driver->deleteMultiple($keys); } - protected function doHas($key) + public function doHas($key) { return $this->driver->contains($key); } diff --git a/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php index b9dd35ac0..f551c8044 100644 --- a/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php +++ b/system/src/Grav/Framework/Cache/Adapter/MemoryCache.php @@ -12,12 +12,15 @@ use Grav\Framework\Cache\AbstractCache; /** * Cache class for PSR-16 compatible "Simple Cache" implementation using in memory backend. - * Memory backend does not use namespace or ttl as the cache is unique to each cache object and request. + * Memory backend does not use namespace or default ttl as the cache is unique to each cache object and request. * * @package Grav\Framework\Cache */ class MemoryCache extends AbstractCache { + /** + * @var array + */ protected $cache = []; /** @@ -28,34 +31,39 @@ class MemoryCache extends AbstractCache parent::__construct(); } - protected function doGet($key, $default) + public function doGet($key) { - return $this->doHas($key) ? $this->cache[$key] : $default; + if (!array_key_exists($key, $this->cache)) { + // Cache misses. + $this->cache[$key] = $this->miss(); + } + + return $this->cache[$key]; } - protected function doSet($key, $value, $ttl) + public function doSet($key, $value, $ttl) { $this->cache[$key] = $value; return true; } - protected function doDelete($key) + public function doDelete($key) { - unset($this->cache[$key]); + $this->cache[$key] = $this->miss(); return true; } - protected function doClear() + public function doClear() { $this->cache = []; return true; } - protected function doHas($key) + public function doHas($key) { - return array_key_exists($key, $this->cache); + return array_key_exists($key, $this->cache) && $this->isHit($this->cache[$key]); } } diff --git a/system/src/Grav/Framework/Cache/CacheInterface.php b/system/src/Grav/Framework/Cache/CacheInterface.php index fe573013a..3ba8a3ef4 100644 --- a/system/src/Grav/Framework/Cache/CacheInterface.php +++ b/system/src/Grav/Framework/Cache/CacheInterface.php @@ -16,4 +16,12 @@ use Psr\SimpleCache\CacheInterface as SimpleCacheInterface; */ interface CacheInterface extends SimpleCacheInterface { + public function doGet($key); + public function doSet($key, $value, $ttl); + public function doDelete($key); + public function doClear(); + public function doGetMultiple($keys); + public function doSetMultiple($values, $ttl); + public function doDeleteMultiple($keys); + public function doHas($key); } diff --git a/system/src/Grav/Framework/Cache/CacheMiss.php b/system/src/Grav/Framework/Cache/CacheMiss.php new file mode 100644 index 000000000..0858aec22 --- /dev/null +++ b/system/src/Grav/Framework/Cache/CacheMiss.php @@ -0,0 +1,20 @@ +namespace = (string) $namespace; $this->defaultLifetime = $this->convertTtl($defaultLifetime, true); + $this->miss = new CacheMiss(); } /** @@ -55,6 +61,23 @@ trait CacheTrait return $this->defaultLifetime; } + /** + * @param mixed $value + * @return bool + */ + protected function isHit($value) + { + return !($value instanceof CacheMiss); + } + + /** + * @return CacheMiss + */ + protected function miss() + { + return $this->miss; + } + /** * @inheritdoc */ @@ -62,7 +85,9 @@ trait CacheTrait { $this->validateKey($key); - return $this->doGet($key, $default); + $value = $this->doGet($key); + + return $this->isHit($value) ? $value : $default; } /** @@ -113,7 +138,7 @@ trait CacheTrait $this->validateKeys($keys); - $list = $this->doGetMultiple($keys, $default); + $list = $this->doGetMultiple($keys); if (count($list) !== count($keys)) { // Return all values, with default value if they do not exist. @@ -121,9 +146,7 @@ trait CacheTrait } // Make sure that results are returned in the same order as the keys were given. - ksort($list); - - return $list; + return array_replace(array_flip($keys), $list); } /** @@ -181,6 +204,49 @@ trait CacheTrait return $this->doHas($key); } + abstract public function doGet($key); + abstract public function doSet($key, $value, $ttl); + abstract public function doDelete($key); + abstract public function doClear(); + + public function doGetMultiple($keys) + { + $results = []; + + foreach ($keys as $key) { + $value = $this->doGet($key); + if ($this->isHit($value)) { + $results[$key] = $value; + } + } + + return $results; + } + + public function doSetMultiple($values, $ttl) + { + $success = true; + + foreach ($values as $key => $value) { + $success = $this->doSet($key, $value, $ttl) && $success; + } + + return $success; + } + + public function doDeleteMultiple($keys) + { + $success = true; + + foreach ($keys as $key) { + $success = $this->doDelete($key) && $success; + } + + return $success; + } + + abstract public function doHas($key); + /** * @param string $key */ @@ -228,46 +294,4 @@ trait CacheTrait throw new InvalidArgumentException(sprintf('Expiration date must be an integer, a DateInterval or null, "%s" given', is_object($ttl) ? get_class($ttl) : gettype($ttl))); } - - abstract protected function doGet($key, $default); - abstract protected function doSet($key, $value, $ttl); - abstract protected function doDelete($key); - abstract protected function doClear(); - - protected function doGetMultiple($keys, $default) - { - $results = []; - - foreach ($keys as $key) { - if ($this->doHas($key)) { - $results[$key] = $this->doGet($key, $default); - } - } - - return $results; - } - - protected function doSetMultiple($values, $ttl) - { - $success = true; - - foreach ($values as $key => $value) { - $success = $this->doSet($key, $value, $ttl) && $success; - } - - return $success; - } - - protected function doDeleteMultiple($keys) - { - $success = true; - - foreach ($keys as $key) { - $success = $this->doDelete($key) && $success; - } - - return $success; - } - - abstract protected function doHas($key); }