Skip to content

Commit b52b5d7

Browse files
authored
Merge pull request #15 from byjg/5.0
Add GarbageCollectorInterface.php
2 parents cc010ce + 62e0d2e commit b52b5d7

18 files changed

+599
-124
lines changed

.github/workflows/phpunit.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ jobs:
2424
services:
2525
memcached:
2626
image: memcached
27+
ports:
28+
- "11211:11211"
2729
redis:
2830
image: redis
31+
ports:
32+
- "6379:6379"
2933
options: >-
3034
--health-cmd "redis-cli ping"
3135
--health-interval 10s

README.md

Lines changed: 12 additions & 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
@@ -52,6 +52,17 @@ You can use a PSR-11 compatible to retrieve the cache keys.
5252

5353
See more [here](docs/psr11-usage.md)
5454

55+
## Beyond the PSR protocol
56+
57+
The PSR protocol is a good way to standardize the cache access,
58+
but sometimes you need to go beyond the protocol.
59+
60+
Some cache engines have additional features that are not covered by the PSR protocol.
61+
62+
Some examples are:
63+
- [Atomic Operations](docs/atomic-operations.md)
64+
- [Garbage Collection](docs/garbage-collection.md)
65+
5566
## Install
5667

5768
Just type:

docker-compose.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
services:
2+
memcached:
3+
image: memcached
4+
container_name: memcached
5+
ports:
6+
- "11211:11211"
7+
8+
redis:
9+
image: redis
10+
container_name: redis
11+
ports:
12+
- "6379:6379"
13+
healthcheck:
14+
test: ["CMD", "redis-cli", "ping"]
15+
interval: 10s
16+
timeout: 5s
17+
retries: 5

docs/atomic-operations.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Atomic Operations
2+
3+
Some cache engines allow you to do atomic operations such as incrementing or decrementing a value.
4+
5+
Besides this is not cache operation, it is a common operation in cache engines.
6+
7+
The advantage of using atomic operations is that you can avoid race conditions when multiple processes
8+
are trying to update the same value.
9+
10+
The atomic operations are:
11+
- Increment: Increment a value by a given number
12+
- Decrement: Decrement a value by a given number
13+
- Add: Add a value to a list in the cache
14+
15+
The engines that support atomic operations have to implement the `AtomicOperationInterface`.
16+
17+
Some engines that support atomic operations are:
18+
- RedisCachedEngine
19+
- MemcachedEngine
20+
- TmpfsCacheEngine
21+
- FileSystemCacheEngine
22+
23+
## Increment
24+
25+
The increment operation is used to increment a value by a given number.
26+
27+
```php
28+
<?php
29+
/** @var \ByJG\Cache\AtomicOperationInterface $cache */
30+
$cache->increment('my-key', 1);
31+
```
32+
33+
## Decrement
34+
35+
The decrement operation is used to decrement a value by a given number.
36+
37+
```php
38+
<?php
39+
/** @var \ByJG\Cache\AtomicOperationInterface $cache */
40+
$cache->decrement('my-key', 1);
41+
```
42+
43+
## Add
44+
45+
The add operation is used to add a value to a list in the cache.
46+
47+
```php
48+
<?php
49+
/** @var \ByJG\Cache\AtomicOperationInterface $cache */
50+
$cache->add('my-key', 'value1');
51+
$cache->add('my-key', 'value2');
52+
$cache->add('my-key', 'value3');
53+
54+
print_r($cache->get('my-key')); // ['value1', 'value2', 'value3']
55+
```
56+

docs/garbage-collection.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Garbage Collection
2+
3+
Some cache engines need to have a garbage collection process to remove the expired keys.
4+
5+
In some engines like `Memcached` and `Redis` the garbage collection is done automatically by the engine itself.
6+
7+
In other engines like `FileSystem` and `Array` there is no such process. The current implementation
8+
is based on the Best Effort. It means an expired key is removed only when you try to access it.
9+
10+
If the cache engine has a low hit rate, it is recommended to run a garbage collection process
11+
to avoid the cache to grow indefinitely.
12+
13+
The classes that implement the `GarbageCollectionInterface` have the method `collectGarbage()`.
14+
15+
Some engines that support garbage collection are:
16+
- FileSystemCacheEngine
17+
- ArrayCacheEngine
18+
- TmpfsCacheEngine
19+
20+
## Example
21+
22+
```php
23+
<?php
24+
/** @var \ByJG\Cache\GarbageCollectionInterface $cache */
25+
$cache->collectGarbage();
26+
```
27+
28+
Note: The garbage collection process is blocking.
29+
It means the process will be slow if you have a lot of keys to remove.
30+

src/AtomicOperationInterface.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace ByJG\Cache;
4+
5+
use DateInterval;
6+
7+
interface AtomicOperationInterface
8+
{
9+
public function increment(string $key, int $value = 1, DateInterval|int|null $ttl = null): int;
10+
11+
public function decrement(string $key, int $value = 1, DateInterval|int|null $ttl = null): int;
12+
13+
public function add(string $key, $value, DateInterval|int|null $ttl = null): array;
14+
}

src/CacheLockInterface.php

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

src/Factory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ public static function createRedisCacheEngine(?string $servers = null, ?string $
7070
);
7171
}
7272

73-
public static function createTmpfsCachePool(?LoggerInterface $logger = null): CachePool
73+
public static function createTmpfsCachePool(string $prefix = 'cache', ?LoggerInterface $logger = null): CachePool
7474
{
7575
return new CachePool(
76-
new TmpfsCacheEngine($logger)
76+
new TmpfsCacheEngine($prefix, $logger)
7777
);
7878
}
7979

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
}

0 commit comments

Comments
 (0)