Skip to content

Commit ea83943

Browse files
committed
Add GarbageCollectorInterface.php
1 parent cc010ce commit ea83943

10 files changed

+143
-99
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ PSR-6 Getting Started: [here](docs/basic-usage-psr6-cachepool.md)
3737
| [\ByJG\Cache\Psr16\TmpfsCacheEngine](docs/class-tmpfs-cache-engine.md) | Uses the Tmpfs as the cache engine |
3838
| [\ByJG\Cache\Psr16\RedisCachedEngine](docs/class-redis-cache-engine.md) | uses the Redis as cache |
3939
| [\ByJG\Cache\Psr16\SessionCachedEngine](docs/class-session-cache-engine.md) | uses the PHP session as cache |
40-
| [\ByJG\Cache\Psr16\ShmopCachedEngine](docs/class-shmop-cache-engine.md) | uses the shared memory area for cache |
40+
| [\ByJG\Cache\Psr16\ShmopCacheEngine](docs/class-shmop-cache-engine.md) (deprecated) | uses the shared memory area for cache. Use TmpfsCacheEngine. |
4141

4242

4343
## Logging cache commands

src/CacheLockInterface.php

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/GarbageCollectorInterface.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace ByJG\Cache;
4+
5+
interface GarbageCollectorInterface
6+
{
7+
public function collectGarbage();
8+
9+
public function getTtl(string $key): ?int;
10+
}

src/Psr16/ArrayCacheEngine.php

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@
33
namespace ByJG\Cache\Psr16;
44

55
use ByJG\Cache\Exception\InvalidArgumentException;
6+
use ByJG\Cache\GarbageCollectorInterface;
67
use DateInterval;
78
use Psr\Container\ContainerExceptionInterface;
89
use Psr\Container\NotFoundExceptionInterface;
910
use Psr\Log\LoggerInterface;
1011
use Psr\Log\NullLogger;
1112

12-
class ArrayCacheEngine extends BaseCacheEngine
13+
class ArrayCacheEngine extends BaseCacheEngine implements GarbageCollectorInterface
1314
{
1415

15-
protected array $cache = [];
16+
protected array $cache = [
17+
"ttl" => []
18+
];
1619

1720
protected LoggerInterface|null $logger = null;
1821

@@ -41,7 +44,7 @@ public function has(string $key): bool
4144
{
4245
$key = $this->getKeyFromContainer($key);
4346
if (isset($this->cache[$key])) {
44-
if (isset($this->cache["$key.ttl"]) && time() >= $this->cache["$key.ttl"]) {
47+
if (isset($this->cache['ttl']["$key"]) && time() >= $this->cache["ttl"]["$key"]) {
4548
$this->delete($key);
4649
return false;
4750
}
@@ -93,7 +96,7 @@ public function set(string $key, mixed $value, DateInterval|int|null $ttl = null
9396

9497
$this->cache[$key] = serialize($value);
9598
if (!empty($ttl)) {
96-
$this->cache["$key.ttl"] = $this->addToNow($ttl);
99+
$this->cache["ttl"]["$key"] = $this->addToNow($ttl);
97100
}
98101

99102
return true;
@@ -116,12 +119,32 @@ public function delete(string $key): bool
116119
$key = $this->getKeyFromContainer($key);
117120

118121
unset($this->cache[$key]);
119-
unset($this->cache["$key.ttl"]);
122+
unset($this->cache["ttl"]["$key"]);
120123
return true;
121124
}
122125

123126
public function isAvailable(): bool
124127
{
125128
return true;
126129
}
130+
131+
public function collectGarbage()
132+
{
133+
foreach ($this->cache["ttl"] as $key => $ttl) {
134+
if (time() >= $ttl) {
135+
unset($this->cache[$key]);
136+
unset($this->cache["ttl"]["$key"]);
137+
}
138+
}
139+
}
140+
141+
public function getTtl(string $key): ?int
142+
{
143+
$key = $this->getKeyFromContainer($key);
144+
if (isset($this->cache["ttl"]["$key"])) {
145+
return $this->cache["ttl"]["$key"];
146+
}
147+
148+
return null;
149+
}
127150
}

src/Psr16/FileSystemCacheEngine.php

Lines changed: 68 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
namespace ByJG\Cache\Psr16;
44

5-
use ByJG\Cache\CacheLockInterface;
5+
use ByJG\Cache\GarbageCollectorInterface;
66
use DateInterval;
77
use Exception;
88
use Psr\Container\ContainerExceptionInterface;
99
use Psr\Container\NotFoundExceptionInterface;
1010
use Psr\Log\LoggerInterface;
1111
use Psr\Log\NullLogger;
12-
use Psr\SimpleCache\InvalidArgumentException;
1312

14-
class FileSystemCacheEngine extends BaseCacheEngine implements CacheLockInterface
13+
class FileSystemCacheEngine extends BaseCacheEngine implements GarbageCollectorInterface
1514
{
1615

1716
protected ?LoggerInterface $logger = null;
@@ -45,29 +44,11 @@ public function get(string $key, mixed $default = null): mixed
4544
{
4645
// Check if file is Locked
4746
$fileKey = $this->fixKey($key);
48-
$lockFile = $fileKey . ".lock";
49-
if (file_exists($lockFile)) {
50-
$this->logger->info("[Filesystem cache] Locked! $key. Waiting...");
51-
$lockTime = filemtime($lockFile);
52-
53-
while (true) {
54-
if (!file_exists($lockFile)) {
55-
$this->logger->info("[Filesystem cache] Lock released for '$key'");
56-
break;
57-
}
58-
if (intval(time() - $lockTime) > 20) { // Wait for 10 seconds
59-
$this->logger->info("[Filesystem cache] Gave up to wait unlock. Release lock for '$key'");
60-
$this->unlock($key);
61-
return $default;
62-
}
63-
sleep(1); // 1 second
64-
}
65-
}
6647

6748
// Check if file exists
6849
if ($this->has($key)) {
6950
$this->logger->info("[Filesystem cache] Get '$key'");
70-
return unserialize(file_get_contents($fileKey));
51+
return $this->getContents($fileKey, $default);
7152
} else {
7253
$this->logger->info("[Filesystem cache] Not found '$key'");
7354
return $default;
@@ -108,12 +89,7 @@ public function set(string $key, mixed $value, DateInterval|int|null $ttl = null
10889
if (is_string($value) && (strlen($value) === 0)) {
10990
touch($fileKey);
11091
} else {
111-
file_put_contents($fileKey, serialize($value));
112-
}
113-
114-
$validUntil = $this->addToNow($ttl);
115-
if (!empty($validUntil)) {
116-
file_put_contents($fileKey . ".ttl", (string)$validUntil);
92+
$this->putContents($fileKey, $value, $this->addToNow($ttl));
11793
}
11894
} catch (Exception $ex) {
11995
$this->logger->warning("[Filesystem cache] I could not write to cache on file '" . basename($key) . "'. Switching to nocache=true mode.");
@@ -133,39 +109,6 @@ public function delete(string $key): bool
133109
return true;
134110
}
135111

136-
/**
137-
* Lock resource before set it.
138-
* @param string $key
139-
*/
140-
public function lock(string $key): void
141-
{
142-
$this->logger->info("[Filesystem cache] Lock '$key'");
143-
144-
$lockFile = $this->fixKey($key) . ".lock";
145-
146-
try {
147-
file_put_contents($lockFile, date('c'));
148-
} catch (Exception $ex) {
149-
// Ignoring... Set will cause an error
150-
}
151-
}
152-
153-
/**
154-
* UnLock resource after set it.
155-
* @param string $key
156-
*/
157-
public function unlock(string $key): void
158-
{
159-
160-
$this->logger->info("[Filesystem cache] Unlock '$key'");
161-
162-
$lockFile = $this->fixKey($key) . ".lock";
163-
164-
if (file_exists($lockFile)) {
165-
unlink($lockFile);
166-
}
167-
}
168-
169112
/**
170113
* @throws ContainerExceptionInterface
171114
* @throws NotFoundExceptionInterface
@@ -227,9 +170,10 @@ public function clear(): bool
227170
public function has(string $key): bool
228171
{
229172
$fileKey = $this->fixKey($key);
173+
$fileTtl = null;
230174
if (file_exists($fileKey)) {
231175
if (file_exists("$fileKey.ttl")) {
232-
$fileTtl = intval(file_get_contents("$fileKey.ttl"));
176+
$fileTtl = intval($this->getContents("$fileKey.ttl"));
233177
}
234178

235179
if (!empty($fileTtl) && time() >= $fileTtl) {
@@ -244,4 +188,66 @@ public function has(string $key): bool
244188

245189
return false;
246190
}
191+
192+
protected function getContents(string $fileKey, mixed $default = null): mixed
193+
{
194+
if (!file_exists($fileKey)) {
195+
return $default;
196+
}
197+
198+
$fo = fopen($fileKey, 'r');
199+
$waitIfLocked = 1;
200+
$lock = flock($fo, LOCK_EX, $waitIfLocked);
201+
try {
202+
$content = unserialize(file_get_contents($fileKey));
203+
} finally {
204+
flock($fo, LOCK_UN);
205+
fclose($fo);
206+
}
207+
208+
return $content;
209+
}
210+
211+
protected function putContents(string $fileKey, mixed $value, ?string $ttl): void
212+
{
213+
$fo = fopen($fileKey, 'w');
214+
$waitIfLocked = 1;
215+
$lock = flock($fo, LOCK_EX, $waitIfLocked);
216+
try {
217+
file_put_contents($fileKey, serialize($value));
218+
if (!is_null($ttl)) {
219+
file_put_contents("$fileKey.ttl", serialize($ttl));
220+
}
221+
} finally {
222+
flock($fo, LOCK_UN);
223+
fclose($fo);
224+
}
225+
}
226+
227+
public function collectGarbage()
228+
{
229+
$patternKey = $this->fixKey('*');
230+
$list = glob("$patternKey.ttl");
231+
foreach ($list as $file) {
232+
$fileTtl = intval($this->getContents($file));
233+
if (time() >= $fileTtl) {
234+
$fileContent = str_replace('.ttl', '', $file);
235+
if (file_exists($fileContent)) {
236+
unlink($fileContent);
237+
}
238+
unlink($file);
239+
}
240+
}
241+
return true;
242+
}
243+
244+
245+
public function getTtl(string $key): ?int
246+
{
247+
$fileKey = $this->fixKey($key);
248+
if (file_exists("$fileKey.ttl")) {
249+
return intval($this->getContents("$fileKey.ttl"));
250+
}
251+
return null;
252+
}
247253
}

src/Psr16/NoCacheEngine.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@
22

33
namespace ByJG\Cache\Psr16;
44

5-
use ByJG\Cache\CacheLockInterface;
65
use ByJG\Cache\Exception\InvalidArgumentException;
76
use DateInterval;
87
use Psr\Container\ContainerExceptionInterface;
98
use Psr\Container\NotFoundExceptionInterface;
109

11-
class NoCacheEngine extends BaseCacheEngine implements CacheLockInterface
10+
class NoCacheEngine extends BaseCacheEngine
1211
{
1312
/**
1413
* @param string $key

src/Psr16/ShmopCacheEngine.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
* min seg size (bytes) = 1
2525
*
2626
*
27+
* @deprecated Use TmpfsCacheEngine instead
2728
*/
2829
class ShmopCacheEngine extends BaseCacheEngine
2930
{

src/Psr16/TmpfsCacheEngine.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@
22

33
namespace ByJG\Cache\Psr16;
44

5-
use ByJG\Cache\CacheLockInterface;
6-
use ByJG\Cache\Exception\InvalidArgumentException;
7-
use DateInterval;
8-
use Psr\Container\ContainerExceptionInterface;
9-
use Psr\Container\NotFoundExceptionInterface;
105
use Psr\Log\LoggerInterface;
116

127
class TmpfsCacheEngine extends FileSystemCacheEngine

tests/BaseCacheTest.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ public function CachePoolProvider()
3333
'FileSystem' => [
3434
new \ByJG\Cache\Psr16\FileSystemCacheEngine()
3535
],
36+
'Tmpfs' => [
37+
new \ByJG\Cache\Psr16\TmpfsCacheEngine()
38+
],
3639
'ShmopCache' => [
3740
new \ByJG\Cache\Psr16\ShmopCacheEngine()
3841
],

tests/CachePSR16Test.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Tests;
44

55
use ByJG\Cache\Exception\InvalidArgumentException;
6+
use ByJG\Cache\GarbageCollectorInterface;
67
use ByJG\Cache\Psr16\BaseCacheEngine;
78
use ByJG\Cache\Psr16\NoCacheEngine;
89

@@ -238,4 +239,34 @@ public function testCacheContainerKey(BaseCacheEngine $cacheEngine)
238239
}
239240
}
240241

242+
/**
243+
* @dataProvider CachePoolProvider
244+
*/
245+
public function testGarbageCollector(BaseCacheEngine $cacheEngine)
246+
{
247+
$this->cacheEngine = $cacheEngine;
248+
249+
if ($cacheEngine->isAvailable() && ($cacheEngine instanceof GarbageCollectorInterface)) {
250+
// First time
251+
$cacheEngine->set('chave', "ok");
252+
$this->assertTrue($cacheEngine->has('chave'));
253+
$this->assertNull($cacheEngine->getTtl('chave'));
254+
$cacheEngine->delete('chave');
255+
$this->assertFalse($cacheEngine->has('chave'));
256+
257+
// Set TTL
258+
$cacheEngine->set('chave', "ok", 1);
259+
$this->assertTrue($cacheEngine->has('chave'));
260+
$this->assertNotNull($cacheEngine->getTtl('chave'));
261+
$cacheEngine->collectGarbage();
262+
$this->assertTrue($cacheEngine->has('chave'));
263+
$this->assertNotNull($cacheEngine->getTtl('chave')); // Should not delete yet
264+
sleep(1);
265+
$cacheEngine->collectGarbage();
266+
$this->assertNull($cacheEngine->getTtl('chave')); // Should be deleted
267+
$this->assertFalse($cacheEngine->has('chave'));
268+
} else {
269+
$this->markTestIncomplete('Does not support garbage collector or it is native');
270+
}
271+
}
241272
}

0 commit comments

Comments
 (0)