Skip to content
Closed

TTL #28

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@ This example fetches the value of the key `foo` and passes it to the
`var_dump` function. You can use any of the composition provided by
[promises](https://github.com/reactphp/promise).

If the key `foo` does not exist or when the TTL has passed, the promise will
be fulfilled with `null` as value. On any error it will also resolve with `null`.

#### set()

```php
$cache->set('foo', 'bar');
$cache->set('foo', 'bar', 60);
```

This example eventually sets the value of the key `foo` to `bar`. If it
Expand Down
56 changes: 48 additions & 8 deletions src/ArrayCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,18 @@
namespace React\Cache;

use React\Promise;
use SplPriorityQueue;

class ArrayCache implements CacheInterface
{
private $limit;
private $data = array();
private $expires = array();

/**
* @var SplPriorityQueue
*/
private $expiresQueue;

/**
* The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
Expand Down Expand Up @@ -39,10 +46,16 @@ class ArrayCache implements CacheInterface
public function __construct($limit = null)
{
$this->limit = $limit;
$this->expiresQueue = new SplPriorityQueue();
$this->expiresQueue->setExtractFlags(SplPriorityQueue::EXTR_BOTH);
}

public function get($key, $default = null)
{
if (array_key_exists($key, $this->expires)) {
$this->garbageCollection();
}

if (!array_key_exists($key, $this->data)) {
return Promise\resolve($default);
}
Expand All @@ -51,28 +64,55 @@ public function get($key, $default = null)
$value = $this->data[$key];
unset($this->data[$key]);
$this->data[$key] = $value;

return Promise\resolve($value);
}

public function set($key, $value)
public function set($key, $value, $ttl = null)
{
$expires = null;

if (is_int($ttl)) {
$this->expires[$key] = microtime(true) + $ttl;
$this->expiresQueue->insert($key, 0 - $this->expires[$key]);
}

// unset before setting to ensure this entry will be added to end of array
unset($this->data[$key]);
$this->data[$key] = $value;

// ensure size limit is not exceeded or remove first entry from array
if ($this->limit !== null && count($this->data) > $this->limit) {
reset($this->data);
unset($this->data[key($this->data)]);
}
$this->garbageCollection();

return Promise\resolve(true);
}

public function remove($key)
{
unset($this->data[$key]);
unset($this->data[$key], $this->expires[$key]);
$this->garbageCollection();
return Promise\resolve(true);
}

private function garbageCollection()
{
// ensure size limit is not exceeded or remove first entry from array
while ($this->limit !== null && count($this->data) > $this->limit) {
reset($this->data);
unset($this->data[key($this->data)]);
}

if ($this->expiresQueue->count() === 0) {
return;
}

$this->expiresQueue->rewind();
do {
$run = false;
$item = $this->expiresQueue->current();
if ((int)substr((string)$item['priority'], 1) <= microtime(true)) {
$this->expiresQueue->extract();
$run = true;
unset($this->data[$item['data']], $this->expires[$item['data']]);
}
} while ($run && $this->expiresQueue->count() > 0);
}
}
3 changes: 2 additions & 1 deletion src/CacheInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ public function get($key, $default = null);
*
* @param string $key
* @param mixed $value
* @param float|null $ttl
* @return PromiseInterface Returns a promise which resolves to true on success of false on error
*/
public function set($key, $value);
public function set($key, $value, $ttl = null);

/**
* Remove an item from the cache, returns a promise which resolves to true on success or
Expand Down
43 changes: 43 additions & 0 deletions tests/ArrayCacheTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,47 @@ public function testGetWithLimitedSizeWillUpdateLRUInfo()
$this->cache->get('bar')->then($this->expectCallableOnceWith(null));
$this->cache->get('baz')->then($this->expectCallableOnceWith('3'));
}

/** @test */
public function getWithinTtl()
{
$this->cache
->set('foo', 'bar', 100);


$success = $this->createCallableMock();
$success
->expects($this->once())
->method('__invoke')
->with('bar');

$this->cache
->get('foo')
->then(
$success,
$this->expectCallableNever()
);
}

/** @test */
public function getAfterTtl()
{
$this->cache
->set('foo', 'bar', 1);

sleep(2);

$success = $this->createCallableMock();
$success
->expects($this->once())
->method('__invoke')
->with(null);

$this->cache
->get('foo')
->then(
$success,
$this->expectCallableNever()
);
}
}