15
15
use Psr \Log \LoggerAwareInterface ;
16
16
use Psr \Log \LoggerAwareTrait ;
17
17
use Symfony \Component \Cache \CacheItem ;
18
+ use Symfony \Component \Cache \Exception \InvalidArgumentException ;
18
19
use Symfony \Component \Cache \ResettableInterface ;
19
20
use Symfony \Contracts \Cache \CacheInterface ;
20
21
21
22
/**
23
+ * An in-memory cache storage.
24
+ *
25
+ * Acts as a least-recently-used (LRU) storage when configured with a maximum number of items.
26
+ *
22
27
* @author Nicolas Grekas <p@tchwork.com>
23
28
*/
24
29
class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInterface, ResettableInterface
@@ -29,13 +34,25 @@ class ArrayAdapter implements AdapterInterface, CacheInterface, LoggerAwareInter
29
34
private $ values = [];
30
35
private $ expiries = [];
31
36
private $ createCacheItem ;
37
+ private $ maxLifetime ;
38
+ private $ maxItems ;
32
39
33
40
/**
34
41
* @param bool $storeSerialized Disabling serialization can lead to cache corruptions when storing mutable values but increases performance otherwise
35
42
*/
36
- public function __construct (int $ defaultLifetime = 0 , bool $ storeSerialized = true )
43
+ public function __construct (int $ defaultLifetime = 0 , bool $ storeSerialized = true , int $ maxLifetime = 0 , int $ maxItems = 0 )
37
44
{
45
+ if (0 > $ maxLifetime ) {
46
+ throw new InvalidArgumentException (sprintf ('Argument $maxLifetime must be a positive integer, %d passed. ' , $ maxLifetime ));
47
+ }
48
+
49
+ if (0 > $ maxItems ) {
50
+ throw new InvalidArgumentException (sprintf ('Argument $maxItems must be a positive integer, %d passed. ' , $ maxItems ));
51
+ }
52
+
38
53
$ this ->storeSerialized = $ storeSerialized ;
54
+ $ this ->maxLifetime = $ maxLifetime ;
55
+ $ this ->maxItems = $ maxItems ;
39
56
$ this ->createCacheItem = \Closure::bind (
40
57
static function ($ key , $ value , $ isHit ) use ($ defaultLifetime ) {
41
58
$ item = new CacheItem ();
@@ -84,6 +101,13 @@ public function delete(string $key): bool
84
101
public function hasItem ($ key )
85
102
{
86
103
if (\is_string ($ key ) && isset ($ this ->expiries [$ key ]) && $ this ->expiries [$ key ] > microtime (true )) {
104
+ if ($ this ->maxItems ) {
105
+ // Move the item last in the storage
106
+ $ value = $ this ->values [$ key ];
107
+ unset($ this ->values [$ key ]);
108
+ $ this ->values [$ key ] = $ value ;
109
+ }
110
+
87
111
return true ;
88
112
}
89
113
CacheItem::validateKey ($ key );
@@ -97,7 +121,12 @@ public function hasItem($key)
97
121
public function getItem ($ key )
98
122
{
99
123
if (!$ isHit = $ this ->hasItem ($ key )) {
100
- $ this ->values [$ key ] = $ value = null ;
124
+ $ value = null ;
125
+
126
+ if (!$ this ->maxItems ) {
127
+ // Track misses in non-LRU mode only
128
+ $ this ->values [$ key ] = null ;
129
+ }
101
130
} else {
102
131
$ value = $ this ->storeSerialized ? $ this ->unfreeze ($ key , $ isHit ) : $ this ->values [$ key ];
103
132
}
@@ -164,7 +193,9 @@ public function save(CacheItemInterface $item)
164
193
$ value = $ item ["\0* \0value " ];
165
194
$ expiry = $ item ["\0* \0expiry " ];
166
195
167
- if (null !== $ expiry && $ expiry <= microtime (true )) {
196
+ $ now = microtime (true );
197
+
198
+ if (null !== $ expiry && $ expiry <= $ now ) {
168
199
$ this ->deleteItem ($ key );
169
200
170
201
return true ;
@@ -173,7 +204,23 @@ public function save(CacheItemInterface $item)
173
204
return false ;
174
205
}
175
206
if (null === $ expiry && 0 < $ item ["\0* \0defaultLifetime " ]) {
176
- $ expiry = microtime (true ) + $ item ["\0* \0defaultLifetime " ];
207
+ $ expiry = $ item ["\0* \0defaultLifetime " ];
208
+ $ expiry = $ now + ($ expiry > ($ this ->maxLifetime ?: $ expiry ) : $ this ->maxLifetime : $ expiry );
209
+ } elseif ($ this ->maxLifetime && (null === $ expiry || $ expiry > $ now + $ this ->maxLifetime )) {
210
+ $ expiry = $ now + $ this ->maxLifetime ;
211
+ }
212
+
213
+ if ($ this ->maxItems ) {
214
+ unset($ this ->values [$ key ]);
215
+
216
+ // Iterate items and vacuum expired ones while we are at it
217
+ foreach ($ this ->values as $ k => $ v ) {
218
+ if ($ this ->expiries [$ k ] > $ now && \count ($ this ->values ) < $ this ->maxItems ) {
219
+ break ;
220
+ }
221
+
222
+ unset($ this ->values [$ k ], $ this ->expiries [$ k ]);
223
+ }
177
224
}
178
225
179
226
$ this ->values [$ key ] = $ value ;
@@ -210,15 +257,21 @@ public function commit()
210
257
public function clear (string $ prefix = '' )
211
258
{
212
259
if ('' !== $ prefix ) {
260
+ $ now = microtime (true );
261
+
213
262
foreach ($ this ->values as $ key => $ value ) {
214
- if (0 === strpos ($ key , $ prefix )) {
263
+ if (! isset ( $ this -> expiries [ $ key ]) || $ this -> expiries [ $ key ] <= $ now || 0 === strpos ($ key , $ prefix )) {
215
264
unset($ this ->values [$ key ], $ this ->expiries [$ key ]);
216
265
}
217
266
}
218
- } else {
219
- $ this ->values = $ this ->expiries = [];
267
+
268
+ if ($ this ->values ) {
269
+ return true ;
270
+ }
220
271
}
221
272
273
+ $ this ->values = $ this ->expiries = [];
274
+
222
275
return true ;
223
276
}
224
277
@@ -258,8 +311,20 @@ private function generateItems(array $keys, $now, $f)
258
311
{
259
312
foreach ($ keys as $ i => $ key ) {
260
313
if (!$ isHit = isset ($ this ->expiries [$ key ]) && ($ this ->expiries [$ key ] > $ now || !$ this ->deleteItem ($ key ))) {
261
- $ this ->values [$ key ] = $ value = null ;
314
+ $ value = null ;
315
+
316
+ if (!$ this ->maxItems ) {
317
+ // Track misses in non-LRU mode only
318
+ $ this ->values [$ key ] = null ;
319
+ }
262
320
} else {
321
+ if ($ this ->maxItems ) {
322
+ // Move the item last in the storage
323
+ $ value = $ this ->values [$ key ];
324
+ unset($ this ->values [$ key ]);
325
+ $ this ->values [$ key ] = $ value ;
326
+ }
327
+
263
328
$ value = $ this ->storeSerialized ? $ this ->unfreeze ($ key , $ isHit ) : $ this ->values [$ key ];
264
329
}
265
330
unset($ keys [$ i ]);
@@ -314,8 +379,12 @@ private function unfreeze(string $key, bool &$isHit)
314
379
$ value = false ;
315
380
}
316
381
if (false === $ value ) {
317
- $ this -> values [ $ key ] = $ value = null ;
382
+ $ value = null ;
318
383
$ isHit = false ;
384
+
385
+ if (!$ this ->maxItems ) {
386
+ $ this ->values [$ key ] = null ;
387
+ }
319
388
}
320
389
}
321
390
0 commit comments