Skip to content

Commit

Permalink
Added Chain cache
Browse files Browse the repository at this point in the history
Closes #66
  • Loading branch information
jasny committed Jun 23, 2018
1 parent 760efd6 commit 3725b9c
Show file tree
Hide file tree
Showing 5 changed files with 455 additions and 1 deletion.
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ if (!isset($value)) {
echo $value;
```

## Cache implementations
## Adapters

* [Apcu](docs/implementations/apcu.md)
* [File](docs/implementations/file.md)
Expand All @@ -57,6 +57,10 @@ echo $value;
* [PhpFile](docs/implementations/phpfile.md)
* [Predis](docs/implementations/predis.md)

The following implementation allows you to combine cache adapters.

* [Chain](docs/implementations/chain.md)

[Other implementations][todo-implementations] are planned. Please vote or
provide a PR to speed up the process of adding the to this library.

Expand Down Expand Up @@ -105,6 +109,8 @@ Persists a set of key => value pairs in the cache
##### `deleteMultiple(array $keys)`
Deletes multiple cache items in a single operation

.

The `Desarrolla2\Cache\CacheInterface` also has the following methods:

##### `withOption(string $key, string $value)`
Expand Down
37 changes: 37 additions & 0 deletions docs/implementations/chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Chain

The Cache chain allows you to use multiple implementations to store cache. For
instance, you can use both fast volatile (in-memory) storage and slower
non-volatile (disk) storage. Alternatively you can have a local storage
as well as a shared storage service.

``` php
use Desarrolla2\Cache\Chain as CacheChain;
use Desarrolla2\Cache\Memory as MemoryCache;
use Desarrolla2\Cache\Predis as PredisCache;

$cache = new CacheChain([
(new MemoryCache())->withOption('ttl', 3600),
(new PredisCache())->withOption('ttl', 10800)
]);
```

The Chain cache implementation doesn't use any option. It uses the `Nop` packer
by default.

Typically it's useful to specify a maximum `ttl` for each implementation. This
means that the volatile memory only holds items that are used often.

The following actions propogate to all cache adapters in the chain

* `set`
* `setMultiple`
* `delete`
* `deleteMultiple`
* `clear`

For the following actions all nodes are tried in sequence

* `has`
* `get`
* `getMultiple`
190 changes: 190 additions & 0 deletions src/Chain.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
<?php
/**
* This file is part of the Cache package.
*
* Copyright (c) Daniel González
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @author Daniel González <[email protected]>
* @author Arnold Daniels <[email protected]>
*/

namespace Desarrolla2\Cache;

use Desarrolla2\Cache\AbstractCache;
use Desarrolla2\Cache\CacheInterface;
use Desarrolla2\Cache\Packer\PackerInterface;
use Desarrolla2\Cache\Exception\InvalidArgumentException;

/**
* Use multiple cache adapters.
*/
class Chain extends AbstractCache
{
/**
* @var CacheInterface[]
*/
protected $adapters;

/**
* Create the default packer for this cache implementation
*
* @return PackerInterface
*/
protected static function createDefaultPacker(): PackerInterface
{
return new NopPacker();
}


/**
* Chain constructor.
*
* @param CacheInterface[] $adapters Fastest to slowest
*/
public function __construct(array $adapters)
{
foreach ($adapters as $adapter) {
if (!$adapter instanceof CacheInterface) {
throw new InvalidArgumentException("All adapters should be a cache implementation");
}
}

$this->adapters = $adapters;
}

/**
* {@inheritdoc}
*/
public function set($key, $value, $ttl = null)
{
$success = true;

foreach ($this->adapters as $adapter) {
$success = $adapter->set($key, $value, $ttl) && $success;
}

return $success;
}

/**
* {@inheritdoc}
*/
public function setMultiple($values, $ttl = null)
{
$success = true;

foreach ($this->adapters as $adapter) {
$success = $adapter->setMultiple($values, $ttl) && $success;
}

return $success;
}

/**
* {@inheritdoc}
*/
public function get($key, $default = null)
{
foreach ($this->adapters as $adapter) {
$result = $adapter->get($key); // Not using $default as we want to get null if the adapter doesn't have it

if (isset($result)) {
return $result;
}
}

return $default;
}

/**
* {@inheritdoc}
*/
public function getMultiple($keys, $default = null)
{
$this->assertIterable($keys, 'keys are not iterable');

$missing = [];
$values = [];

foreach ($keys as $key) {
$this->assertKey($key);

$missing[] = $key;
$values[$key] = $default;
}

foreach ($this->adapters as $adapter) {
if (empty($missing)) {
break;
}

$found = array_filter($adapter->getMultiple($missing), function($value) {
return isset($value);
});

$values = array_merge($values, $found);
$missing = array_diff($missing, array_keys($found));
}

return $values;
}

/**
* {@inheritdoc}
*/
public function has($key)
{
foreach ($this->adapters as $adapter) {
if ($adapter->has($key)) {
return true;
}
}

return false;
}

/**
* {@inheritdoc}
*/
public function delete($key)
{
$success = true;

foreach ($this->adapters as $adapter) {
$success = $adapter->delete($key) && $success;
}

return $success;
}

/**
* {@inheritdoc}
*/
public function deleteMultiple($keys)
{
$success = true;

foreach ($this->adapters as $adapter) {
$success = $adapter->deleteMultiple($keys) && $success;
}

return $success;
}

/**
* {@inheritdoc}
*/
public function clear()
{
$success = true;

foreach ($this->adapters as $adapter) {
$success = $adapter->clear() && $success;
}

return $success;
}
}
Loading

0 comments on commit 3725b9c

Please sign in to comment.