Implement memory and chain cache

This commit is contained in:
Matias Griese
2017-05-29 10:12:02 +03:00
parent 83943ce70d
commit 6dbf704a13
6 changed files with 302 additions and 65 deletions

View File

@@ -0,0 +1,177 @@
<?php
/**
* @package Grav\Framework\Cache
*
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Cache\Adapter;
use Grav\Framework\Cache\AbstractCache;
use Grav\Framework\Cache\CacheInterface;
/**
* Cache class for PSR-16 compatible "Simple Cache" implementation using chained cache adapters.
*
* @package Grav\Framework\Cache
*/
class ChainCache extends AbstractCache
{
/**
* @var array|CacheInterface[]
*/
protected $caches;
/**
* @var int
*/
protected $count;
/**
* @var \stdClass
*/
protected $miss;
/**
* Chain Cache constructor.
* @param array $caches
* @param null|int|\DateInterval $defaultLifetime
*/
public function __construct(array $caches, $defaultLifetime = null)
{
parent::__construct('', $defaultLifetime);
if (!$caches) {
throw new \InvalidArgumentException('At least one cache must be specified');
}
foreach ($caches as $cache) {
if (!$cache instanceof CacheInterface) {
throw new \InvalidArgumentException(sprintf("The class '%s' does not implement the '%s' interface", get_class($cache), CacheInterface::class));
}
}
$this->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;
}
}

View File

@@ -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);
}

View File

@@ -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]);
}
}

View File

@@ -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);
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* @package Grav\Framework\Cache
*
* @copyright Copyright (C) 2014 - 2017 RocketTheme, LLC. All rights reserved.
* @license MIT License; see LICENSE file for details.
*/
namespace Grav\Framework\Cache;
use Grav\Framework\Cache\CacheInterface;
use Grav\Framework\Cache\Exception\InvalidArgumentException;
/**
* Represents Cache Miss
* @package Grav\Framework\Cache
*/
class CacheMiss
{
}

View File

@@ -27,6 +27,11 @@ trait CacheTrait
*/
private $defaultLifetime = null;
/**
* @var CacheMiss
*/
private $miss;
/**
* Always call from constructor.
*
@@ -37,6 +42,7 @@ trait CacheTrait
{
$this->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);
}