Skip to content

Commit 885e52a

Browse files
committed
Add the implementation
1 parent 3704432 commit 885e52a

File tree

14 files changed

+598
-73
lines changed

14 files changed

+598
-73
lines changed

.github/workflows/tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
fail-fast: true
2828
matrix:
2929
php:
30+
- "8.1"
3031
- "8.2"
3132
- "8.3"
3233

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@
33
## [Unreleased]
44

55
<!--
6-
[Unreleased]: https://github.com/beste/array-cache/compare/1.0.0...main
7-
[1.0.0]: https://github.com/beste/array-cache/tree/1.0.0
6+
[Unreleased]: https://github.com/beste/in-memory-cache-php/compare/1.0.0...main
7+
[1.0.0]: https://github.com/beste/in-memory-cache-php/tree/1.0.0
88
-->

README.md

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,49 @@
1-
# PSR-6 Array Cache
1+
# PSR-6 In-Memory Cache
22

3-
A PSR-6 Array Cache
3+
A [PSR-6](https://www.php-fig.org/psr/psr-6/) In-Memory cache that can be used as a default implementation and in tests.
44

5-
<!--
6-
[![Current version](https://img.shields.io/packagist/v/beste/array-cache.svg?logo=composer)](https://packagist.org/packages/beste/array-cache)
7-
[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/beste/array-cache)](https://packagist.org/packages/beste/array-cache)
8-
[![Monthly Downloads](https://img.shields.io/packagist/dm/beste/array-cache.svg)](https://packagist.org/packages/beste/array-cache/stats)
9-
[![Total Downloads](https://img.shields.io/packagist/dt/beste/array-cache.svg)](https://packagist.org/packages/beste/array-cache/stats)
10-
[![Tests](https://github.com/beste/array-cache/actions/workflows/tests.yml/badge.svg)](https://github.com/beste/array-cache/actions/workflows/tests.yml)
11-
-->
5+
[![Current version](https://img.shields.io/packagist/v/beste/in-memory-cache-php.svg?logo=composer)](https://packagist.org/packages/beste/in-memory-cache-php)
6+
[![Packagist PHP Version Support](https://img.shields.io/packagist/php-v/beste/in-memory-cache-php)](https://packagist.org/packages/beste/in-memory-cache-php)
7+
[![Monthly Downloads](https://img.shields.io/packagist/dm/beste/in-memory-cache-php.svg)](https://packagist.org/packages/beste/in-memory-cache-php/stats)
8+
[![Total Downloads](https://img.shields.io/packagist/dt/beste/in-memory-cache-php.svg)](https://packagist.org/packages/beste/in-memory-cache-php/stats)
9+
[![Tests](https://github.com/beste/in-memory-cache-php/actions/workflows/tests.yml/badge.svg)](https://github.com/beste/in-memory-cache-php/actions/workflows/tests.yml)
1210

1311
## Installation
1412

13+
In order to use this cache implementation, you also need to install a [PSR-20](https://www.php-fig.org/psr/psr-20/) [Clock Implementation](https://packagist.org/providers/psr/clock-implementation),
14+
for example, the [`beste/clock`](https://packagist.org/packages/beste/clock).
15+
1516
```shell
16-
composer require beste/array-cache
17+
composer require beste/in-memory-cache beste/clock
18+
```
19+
20+
## Usage
21+
22+
```php
23+
use Beste\Cache\InMemoryCache;
24+
use Beste\Clock\SystemClock;
25+
26+
$clock = SystemClock::create();
27+
$cache = new InMemoryCache($clock);
28+
29+
$item = $cache->getItem('key');
30+
31+
assert($item->isHit() === false);
32+
assert($item->get() === null);
33+
34+
$item->set('value');
35+
$cache->save($item);
36+
37+
// Later...
38+
39+
$item = $cache->getItem('key');
40+
41+
assert($item->isHit() === true);
42+
assert($item->get() === 'value');
1743
```
1844

45+
The test suite
46+
1947
## Running tests
2048

2149
```shell

composer.json

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
{
2-
"name": "beste/array-cache",
3-
"description": "A PSR-6 Array Cache",
4-
"license": "ISC",
2+
"name": "beste/in-memory-cache",
3+
"description": "A PSR-6 In-Memory cache that can be used as a fallback implementation and/or in tests.",
4+
"keywords": ["cache", "psr-6", "beste"],
5+
"license": "MIT",
56
"type": "library",
67
"authors": [
78
{
@@ -10,26 +11,33 @@
1011
}
1112
],
1213
"require": {
13-
"php": "~8.2.0 || ~8.3.0"
14+
"php": "~8.1.0 || ~8.2.0 || ~8.3.0",
15+
"psr/cache": "^2.0 || ^3.0",
16+
"psr/clock": "^1.0",
17+
"psr/clock-implementation": "^1.0"
1418
},
1519
"require-dev": {
16-
"friendsofphp/php-cs-fixer": "^3.40.2",
20+
"beste/clock": "^3.0",
21+
"friendsofphp/php-cs-fixer": "^3.41.0",
1722
"phpstan/extension-installer": "^1.3.1",
18-
"phpstan/phpstan": "^1.10.47",
23+
"phpstan/phpstan": "^1.10.48",
1924
"phpstan/phpstan-deprecation-rules": "^1.1.4",
2025
"phpstan/phpstan-phpunit": "^1.3.15",
2126
"phpstan/phpstan-strict-rules": "^1.5.2",
22-
"phpunit/phpunit": "^10.5.1",
27+
"phpunit/phpunit": "^10.5.2",
2328
"symfony/var-dumper": "^6.4.0"
2429
},
30+
"provide": {
31+
"psr/cache-implementation": "2.0 || 3.0"
32+
},
2533
"autoload": {
2634
"psr-4": {
27-
"Beste\\Psr\\Cache\\": "src"
35+
"Beste\\Cache\\": "src"
2836
}
2937
},
3038
"autoload-dev": {
3139
"psr-4": {
32-
"Beste\\Psr\\Cache\\Tests\\": "tests"
40+
"Beste\\Cache\\Tests\\": "tests"
3341
}
3442
},
3543
"config": {

phpunit.dist.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
33
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
44
bootstrap="vendor/autoload.php"
5-
cacheResultFile=".build/.phpunit.result.cache"
65
colors="true"
76
>
87
<testsuites>

src/CacheItem.php

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
namespace Beste\Cache;
4+
5+
use Psr\Cache\CacheItemInterface;
6+
use Psr\Clock\ClockInterface;
7+
8+
/**
9+
* @internal
10+
*/
11+
final class CacheItem implements CacheItemInterface
12+
{
13+
private mixed $value;
14+
private ?\DateTimeInterface $expiresAt;
15+
private bool $isHit;
16+
17+
public function __construct(private readonly CacheKey $key, private readonly ClockInterface $clock)
18+
{
19+
$this->value = null;
20+
$this->expiresAt = null;
21+
$this->isHit = false;
22+
}
23+
24+
public function getKey(): string
25+
{
26+
return $this->key->toString();
27+
}
28+
29+
public function get(): mixed
30+
{
31+
if ($this->isHit()) {
32+
return $this->value;
33+
}
34+
35+
return null;
36+
}
37+
38+
public function isHit(): bool
39+
{
40+
if ($this->isHit === false) {
41+
return false;
42+
}
43+
44+
if ($this->expiresAt === null) {
45+
return true;
46+
}
47+
48+
return $this->clock->now()->getTimestamp() < $this->expiresAt->getTimestamp();
49+
}
50+
51+
public function set(mixed $value): static
52+
{
53+
$this->isHit = true;
54+
$this->value = $value;
55+
56+
return $this;
57+
}
58+
59+
public function expiresAt(?\DateTimeInterface $expiration): static
60+
{
61+
$this->expiresAt = $expiration;
62+
63+
return $this;
64+
}
65+
66+
public function expiresAfter(\DateInterval|int|null $time): static
67+
{
68+
if ($time === null) {
69+
$this->expiresAt = null;
70+
return $this;
71+
}
72+
73+
if (is_int($time)) {
74+
$time = new \DateInterval("PT{$time}S");
75+
}
76+
77+
$this->expiresAt = $this->clock->now()->add($time);
78+
79+
return $this;
80+
}
81+
}

src/CacheKey.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace Beste\Cache;
4+
5+
/**
6+
* @internal
7+
*/
8+
final class CacheKey
9+
{
10+
private function __construct(private readonly string $value) {}
11+
12+
public static function fromString(string $value): self
13+
{
14+
if (preg_match('/^[a-zA-Z0-9_.]{1,64}$/u', $value) !== 1) {
15+
throw InvalidArgument::invalidKey();
16+
}
17+
18+
return new self($value);
19+
}
20+
21+
public function toString(): string
22+
{
23+
return $this->value;
24+
}
25+
}

src/InMemoryCache.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
<?php
2+
3+
namespace Beste\Cache;
4+
5+
use Psr\Cache\CacheItemInterface;
6+
use Psr\Cache\CacheItemPoolInterface;
7+
use Psr\Clock\ClockInterface;
8+
9+
final class InMemoryCache implements CacheItemPoolInterface
10+
{
11+
/** @var array<string, CacheItemInterface> */
12+
private array $items;
13+
/** @var array<string, CacheItemInterface> */
14+
private array $deferredItems;
15+
16+
public function __construct(private readonly ClockInterface $clock)
17+
{
18+
$this->items = [];
19+
$this->deferredItems = [];
20+
}
21+
22+
public function getItem(string $key): CacheItemInterface
23+
{
24+
$key = CacheKey::fromString($key);
25+
26+
$item = $this->items[$key->toString()] ?? null;
27+
28+
if ($item === null) {
29+
return new CacheItem($key, $this->clock);
30+
}
31+
32+
return clone $item;
33+
}
34+
35+
/**
36+
* @return iterable<CacheItemInterface>
37+
*/
38+
public function getItems(array $keys = []): iterable
39+
{
40+
if ($keys === []) {
41+
return [];
42+
}
43+
44+
$items = [];
45+
46+
foreach ($keys as $key) {
47+
$items[$key] = $this->getItem($key);
48+
}
49+
50+
return $items;
51+
}
52+
53+
public function hasItem(string $key): bool
54+
{
55+
return $this->getItem($key)->isHit();
56+
}
57+
58+
public function clear(): bool
59+
{
60+
$this->items = [];
61+
$this->deferredItems = [];
62+
63+
return true;
64+
}
65+
66+
public function deleteItem(string $key): bool
67+
{
68+
$key = CacheKey::fromString($key);
69+
70+
unset($this->items[$key->toString()]);
71+
72+
return true;
73+
}
74+
75+
public function deleteItems(array $keys): bool
76+
{
77+
foreach ($keys as $key) {
78+
$this->deleteItem($key);
79+
}
80+
81+
return true;
82+
}
83+
84+
public function save(CacheItemInterface $item): bool
85+
{
86+
$this->items[$item->getKey()] = $item;
87+
88+
return true;
89+
}
90+
91+
public function saveDeferred(CacheItemInterface $item): bool
92+
{
93+
$this->deferredItems[$item->getKey()] = $item;
94+
95+
return true;
96+
}
97+
98+
public function commit(): bool
99+
{
100+
foreach ($this->deferredItems as $item) {
101+
$this->save($item);
102+
}
103+
104+
$this->deferredItems = [];
105+
106+
return true;
107+
}
108+
}

src/InvalidArgument.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Beste\Cache;
4+
5+
final class InvalidArgument extends \InvalidArgumentException implements \Psr\Cache\InvalidArgumentException
6+
{
7+
public static function invalidKey(): self
8+
{
9+
return new self('The given key is not valid');
10+
}
11+
}

src/Placeholder.php

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

0 commit comments

Comments
 (0)