Skip to content

Commit 27fea2c

Browse files
authored
Add CacheProviders with tags
Finishing up the CacheProvider contract, and integration with CachedDataSource
2 parents ed40a83 + ebaa212 commit 27fea2c

17 files changed

+758
-119
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ phpcs.xml
2121
.idea/**/.name
2222
.idea/**/codeStyles
2323
.idea/**/php.xml
24+
25+
## Tests
26+
tests/assets/test-sqlite.db

composer.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
"phpcompatibility/phpcompatibility-wp": "^2.1",
4141
"overtrue/phplint": "^3.4"
4242
},
43+
"suggest": {
44+
"ext-pdo": "PDO is required to use the SQLite cache provider"
45+
},
4346
"scripts": {
4447
"suite": [
4548
"composer test",

phpstan.dist.neon

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ includes:
22
- vendor/szepeviktor/phpstan-wordpress/extension.neon
33

44
parameters:
5+
editorUrl: 'phpstorm://open?file=%%file%%&line=%%line%%'
56
level: 5
67
paths:
78
- src

src/Cache/ArrayCacheProvider.php

Lines changed: 52 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use DataKit\DataViews\Clock\Clock;
66
use DataKit\DataViews\Clock\SystemClock;
7+
use DateInterval;
8+
use Exception;
79

810
/**
911
* Cache provider backed by an array.
@@ -12,121 +14,123 @@
1214
*
1315
* @since $ver$
1416
*/
15-
final class ArrayCacheProvider implements CacheProvider {
17+
final class ArrayCacheProvider extends BaseCacheProvider {
1618
/**
1719
* The cached items.
1820
*
1921
* @since $ver$
2022
*
21-
* @var array
23+
* @var CacheItem[]
2224
*/
23-
private array $items;
25+
private array $items = [];
2426

2527
/**
26-
* The clock instance.
28+
* Contains the reference to the tags with their tagged cache keys.
2729
*
2830
* @since $ver$
2931
*
30-
* @var Clock
32+
* @var array<string, string[]>
3133
*/
32-
private Clock $clock;
34+
private array $tags = [];
3335

3436
/**
3537
* Creates an Array cache provider.
3638
*
3739
* @since $ver$
3840
*
3941
* @param Clock|null $clock The clock instance.
40-
* @param array $items The pre-filled cache items.
4142
*/
42-
public function __construct( ?Clock $clock = null, array $items = [] ) {
43+
public function __construct( ?Clock $clock = null ) {
44+
parent::__construct( $clock );
45+
4346
$this->clock = $clock ?? new SystemClock();
44-
$this->items = $items;
4547
}
4648

4749
/**
4850
* @inheritDoc
4951
*
5052
* @since $ver$
5153
*/
52-
public function set( string $key, $value, ?int $ttl = null ): void {
53-
$time = $ttl
54-
? ( $this->clock->now()->getTimestamp() + $ttl )
55-
: null;
54+
public function set( string $key, $value, ?int $ttl = null, array $tags = [] ): void {
55+
try {
56+
$time = (int) $ttl > 0
57+
? ( $this->clock->now()->add( new DateInterval( 'PT' . $ttl . 'S' ) ) )
58+
: null;
59+
} catch ( Exception $e ) {
60+
throw new \InvalidArgumentException( $e->getMessage(), $e->getCode(), $e );
61+
}
5662

57-
$this->items[ $key ] = compact( 'value', 'time' );
63+
$this->items[ $key ] = new CacheItem( $key, $value, $time, $tags );
64+
65+
$this->add_tags( $key, $tags );
5866
}
5967

6068
/**
6169
* @inheritDoc
6270
*
6371
* @since $ver$
6472
*/
65-
public function get( string $key, $fallback = null ) {
66-
$item = $this->items[ $key ] ?? [];
67-
68-
if ( $this->is_expired( $item ) ) {
69-
unset( $this->items[ $key ] );
70-
71-
return $fallback;
72-
}
73+
public function delete( string $key ): bool {
74+
unset( $this->items[ $key ] );
7375

74-
return $item['value'] ?? $fallback;
76+
return true;
7577
}
7678

7779
/**
7880
* @inheritDoc
7981
*
8082
* @since $ver$
8183
*/
82-
public function has( string $key ): bool {
83-
if (
84-
isset( $this->items[ $key ] )
85-
&& ! $this->is_expired( $this->items[ $key ] )
86-
) {
87-
return true;
88-
}
84+
public function delete_by_tags( array $tags ): bool {
85+
foreach ( $tags as $tag ) {
86+
foreach ( $this->tags[ $tag ] ?? [] as $key ) {
87+
$this->delete( $key );
88+
}
8989

90-
unset( $this->items[ $key ] );
90+
unset( $this->tags[ $tag ] );
91+
}
9192

92-
return false;
93+
return true;
9394
}
9495

9596
/**
9697
* @inheritDoc
9798
*
9899
* @since $ver$
99100
*/
100-
public function delete( string $key ): bool {
101-
unset( $this->items[ $key ] );
101+
public function clear(): bool {
102+
$this->items = [];
103+
$this->tags = [];
102104

103105
return true;
104106
}
105107

106108
/**
107-
* @inheritDoc
109+
* Records a key for all provided tags.
108110
*
109111
* @since $ver$
112+
*
113+
* @param string $key The key to tag.
114+
* @param array $tags The tags.
110115
*/
111-
public function clear(): bool {
112-
$this->items = [];
116+
private function add_tags( string $key, array $tags ): void {
117+
foreach ( $tags as $tag ) {
118+
if ( ! is_string( $tag ) ) {
119+
throw new \InvalidArgumentException( 'A tag must be a string.' );
120+
}
113121

114-
return true;
122+
$this->tags[ $tag ] ??= [];
123+
124+
$this->tags[ $tag ] = array_unique( array_merge( $this->tags[ $tag ], [ $key ] ) );
125+
}
115126
}
116127

117128
/**
118-
* Returns whether the provided cache item is expired.
129+
* @inheritDoc
119130
*
120131
* @since $ver$
121-
*
122-
* @param array $item The cache item.
123-
*
124-
* @return bool Whether the cache is expired.
125132
*/
126-
private function is_expired( array $item ): bool {
127-
return (
128-
( $item['time'] ?? null )
129-
&& $this->clock->now()->getTimestamp() > $item['time']
130-
);
133+
protected function doGet( string $key ): ?CacheItem {
134+
return $this->items[ $key ] ?? null;
131135
}
132136
}

src/Cache/BaseCacheProvider.php

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
namespace DataKit\DataViews\Cache;
4+
5+
use DataKit\DataViews\Clock\Clock;
6+
use DataKit\DataViews\Clock\SystemClock;
7+
8+
/**
9+
* An abstract cache provider that implements default logic.
10+
*
11+
* @since $ver$
12+
*/
13+
abstract class BaseCacheProvider implements CacheProvider {
14+
/**
15+
* The clock.
16+
*
17+
* @since $ver$
18+
* @var Clock
19+
*/
20+
protected Clock $clock;
21+
22+
/**
23+
* Creates the base cache provider.
24+
*
25+
* @since $ver$
26+
*
27+
* @param Clock|null $clock The clock.
28+
*/
29+
public function __construct( ?Clock $clock = null ) {
30+
$this->clock = $clock ?? new SystemClock();
31+
}
32+
33+
/**
34+
* Returns the {@see CacheItem} if found by key.
35+
*
36+
* @param string $key The key.
37+
*
38+
* @return CacheItem|null The cache item.
39+
*/
40+
abstract protected function doGet( string $key ): ?CacheItem;
41+
42+
/**
43+
* @inheritDoc
44+
* @since $ver$
45+
*/
46+
public function get( string $key, $fallback = null ) {
47+
$item = $this->doGet( $key );
48+
if ( ! $item || $item->is_expired( $this->clock->now() ) ) {
49+
$this->delete( $key );
50+
51+
return $fallback;
52+
}
53+
54+
return $item->value();
55+
}
56+
57+
/**
58+
* @inheritDoc
59+
* @since $ver$
60+
*/
61+
public function has( string $key ): bool {
62+
$item = $this->doGet( $key );
63+
64+
if ( ! $item || $item->is_expired( $this->clock->now() ) ) {
65+
$this->delete( $key );
66+
67+
return false;
68+
}
69+
70+
return true;
71+
}
72+
}

0 commit comments

Comments
 (0)