Skip to content

Commit 84b79ba

Browse files
committed
Simple LRU implementation for ArrayCache
1 parent 9478319 commit 84b79ba

File tree

4 files changed

+124
-3
lines changed

4 files changed

+124
-3
lines changed

README.md

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,30 @@ or not the item has been removed from cache.
7474

7575
### ArrayCache
7676

77-
The `ArrayCache` provides an in-memory implementation of the
78-
[`CacheInterface`](#cacheinterface).
77+
The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
7978

8079
```php
8180
$cache = new ArrayCache();
8281

8382
$cache->set('foo', 'bar');
8483
```
8584

85+
Its constructor accepts an optional `?int $limit` parameter to limit the
86+
maximum nuber of entries to store in the LRU cache. If you add more
87+
entries to this instance, it will automatically take care of removing
88+
the one that was least recently used (LRU).
89+
90+
For example, this snippet will overwrite the first value and only store
91+
the last two entries:
92+
93+
```php
94+
$cache = new ArrayCache(2);
95+
96+
$cache->set('foo', '1');
97+
$cache->set('bar', '2');
98+
$cache->set('baz', '3');
99+
```
100+
86101
## Common usage
87102

88103
### Fallback get

src/ArrayCache.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,67 @@
66

77
class ArrayCache implements CacheInterface
88
{
9+
private $limit;
910
private $data = array();
1011

12+
/**
13+
* The `ArrayCache` provides an in-memory implementation of the [`CacheInterface`](#cacheinterface).
14+
*
15+
* ```php
16+
* $cache = new ArrayCache();
17+
*
18+
* $cache->set('foo', 'bar');
19+
* ```
20+
*
21+
* Its constructor accepts an optional `?int $limit` parameter to limit the
22+
* maximum nuber of entries to store in the LRU cache. If you add more
23+
* entries to this instance, it will automatically take care of removing
24+
* the one that was least recently used (LRU).
25+
*
26+
* For example, this snippet will overwrite the first value and only store
27+
* the last two entries:
28+
*
29+
* ```php
30+
* $cache = new ArrayCache(2);
31+
*
32+
* $cache->set('foo', '1');
33+
* $cache->set('bar', '2');
34+
* $cache->set('baz', '3');
35+
* ```
36+
*
37+
* @param int|null $limit maximum number of entries to store in the LRU cache
38+
*/
39+
public function __construct($limit = null)
40+
{
41+
$this->limit = $limit;
42+
}
43+
1144
public function get($key)
1245
{
1346
if (!isset($this->data[$key])) {
1447
return Promise\resolve();
1548
}
1649

17-
return Promise\resolve($this->data[$key]);
50+
// remove and append to end of array to keep track of LRU info
51+
$value = $this->data[$key];
52+
unset($this->data[$key]);
53+
$this->data[$key] = $value;
54+
55+
return Promise\resolve($value);
1856
}
1957

2058
public function set($key, $value)
2159
{
60+
// unset before setting to ensure this entry will be added to end of array
61+
unset($this->data[$key]);
2262
$this->data[$key] = $value;
63+
64+
// ensure size limit is not exceeded or remove first entry from array
65+
if ($this->limit !== null && count($this->data) > $this->limit) {
66+
reset($this->data);
67+
unset($this->data[key($this->data)]);
68+
}
69+
2370
return Promise\resolve(true);
2471
}
2572

tests/ArrayCacheTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,52 @@ public function removeShouldRemoveKey()
8282
$this->expectCallableNever()
8383
);
8484
}
85+
86+
public function testLimitSizeToZeroDoesNotStoreAnyData()
87+
{
88+
$this->cache = new ArrayCache(0);
89+
90+
$this->cache->set('foo', 'bar');
91+
92+
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
93+
}
94+
95+
public function testLimitSizeToOneWillOnlyReturnLastWrite()
96+
{
97+
$this->cache = new ArrayCache(1);
98+
99+
$this->cache->set('foo', '1');
100+
$this->cache->set('bar', '2');
101+
102+
$this->cache->get('foo')->then($this->expectCallableOnceWith(null));
103+
$this->cache->get('bar')->then($this->expectCallableOnceWith('2'));
104+
}
105+
106+
public function testOverwriteWithLimitedSizeWillUpdateLRUInfo()
107+
{
108+
$this->cache = new ArrayCache(2);
109+
110+
$this->cache->set('foo', '1');
111+
$this->cache->set('bar', '2');
112+
$this->cache->set('foo', '3');
113+
$this->cache->set('baz', '4');
114+
115+
$this->cache->get('foo')->then($this->expectCallableOnceWith('3'));
116+
$this->cache->get('bar')->then($this->expectCallableOnceWith(null));
117+
$this->cache->get('baz')->then($this->expectCallableOnceWith('4'));
118+
}
119+
120+
public function testGetWithLimitedSizeWillUpdateLRUInfo()
121+
{
122+
$this->cache = new ArrayCache(2);
123+
124+
$this->cache->set('foo', '1');
125+
$this->cache->set('bar', '2');
126+
$this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
127+
$this->cache->set('baz', '3');
128+
129+
$this->cache->get('foo')->then($this->expectCallableOnceWith('1'));
130+
$this->cache->get('bar')->then($this->expectCallableOnceWith(null));
131+
$this->cache->get('baz')->then($this->expectCallableOnceWith('3'));
132+
}
85133
}

tests/TestCase.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ protected function expectCallableOnce()
2626
return $mock;
2727
}
2828

29+
protected function expectCallableOnceWith($param)
30+
{
31+
$mock = $this->createCallableMock();
32+
$mock
33+
->expects($this->once())
34+
->method('__invoke')
35+
->with($param);
36+
37+
return $mock;
38+
}
39+
2940
protected function expectCallableNever()
3041
{
3142
$mock = $this->createCallableMock();

0 commit comments

Comments
 (0)