Skip to content

Commit 5f4e364

Browse files
authored
Fix #42: Refactoring, CacheInterface and add early expiration
* Refactoring and updates to CacheInterface * Add cache stampede protection by early expiration * Move CacheKeyNormalizer functionality to Cache * Move dependency stores to CacheItem * Refactoring of exceptions * Fix comments to Cache and CacheInterface * Change NullCache to PSR-16 implementation * Fix ArrayCache and ArrayCacheTest * Cleanup dependency classes * Change callable signature on PSR-16 in getOrSet() * Use early return in ttlToExpiration()
1 parent ab102cc commit 5f4e364

38 files changed

Lines changed: 1443 additions & 1558 deletions

README.md

Lines changed: 86 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,6 @@
66
<br>
77
</p>
88

9-
This library provides a wrapper around [PSR-16] compatible caching libraries adding more features.
10-
It is used in [Yii Framework] but is usable separately.
11-
12-
[PSR-16]: https://www.php-fig.org/psr/psr-16/
13-
[Yii Framework]: https://www.yiiframework.com/
14-
159
[![Latest Stable Version](https://poser.pugx.org/yiisoft/cache/v/stable.png)](https://packagist.org/packages/yiisoft/cache)
1610
[![Total Downloads](https://poser.pugx.org/yiisoft/cache/downloads.png)](https://packagist.org/packages/yiisoft/cache)
1711
[![Build status](https://github.com/yiisoft/cache/workflows/build/badge.svg)](https://github.com/yiisoft/cache/actions?query=workflow%3Abuild)
@@ -21,59 +15,58 @@ It is used in [Yii Framework] but is usable separately.
2115
[![static analysis](https://github.com/yiisoft/cache/workflows/static%20analysis/badge.svg)](https://github.com/yiisoft/cache/actions?query=workflow%3A%22static+analysis%22)
2216
[![type-coverage](https://shepherd.dev/github/yiisoft/cache/coverage.svg)](https://shepherd.dev/github/yiisoft/cache)
2317

18+
This library is a wrapper around [PSR-16](https://www.php-fig.org/psr/psr-16/) compatible caching libraries
19+
providing own features. It is used in [Yii Framework](https://www.yiiframework.com/) but is usable separately.
20+
2421
## Features
2522

26-
- Built on top of PSR-16, could be used as PSR-16 cache or use any PSR-16 cache as backend.
27-
- Provides multiple cache backends: APC, PHP array, files, memcached, WinCache.
28-
- Customizable way of serializing data. Out of the box PHP, JSON, Igbinary and custom callbacks are supported.
23+
- Built on top of PSR-16, it can use any PSR-16 cache as a handler.
2924
- Ability to set default TTL and key prefix per cache instance.
30-
- Easy to implement your own cache backends extending from `SimpleCache`.
31-
- Adds cache invalidation dependencies on top of PSR-16. Out of the box supports invalidation by tag and invalidation by
32-
file modification time.
33-
- Adds support for `add()` and `addMultiple()` operations additionally to PSR-16.
34-
- Adds handy `getOrSet()` method additionally to PSR-16.
25+
- Provides a built-in behavior to cache stampede prevention.
26+
- Adds cache invalidation dependencies on top of PSR-16.
27+
28+
## Installation
3529

36-
## Configuration
37-
38-
There are two ways to get cache instance. If you need plain PSR-16 instance, you can simply create it:
30+
The package could be installed with composer:
3931

40-
```php
41-
$cache = new ApcuCache();
4232
```
33+
composer install yiisoft/cache
34+
```
35+
36+
## Configuration
4337

44-
If you need additional features such as invalidation dependencies, `add()`, `addMultiple()` or `getOrSet()` you should
45-
wrap PSR-16 cache instance with `Cache`:
38+
There are two ways to get cache instance. If you need PSR-16 instance, you can simply create it:
4639

4740
```php
48-
$cache = new Cache(new ApcuCache());
41+
$arrayCache = new \Yiisoft\Cache\ArrayCache();
4942
```
5043

51-
In order to change default serializer you can use `setSerializer()` method:
44+
If you need a simpler yet more powerful way to cache values based on recomputation callbacks use `getOrSet()` and `remove()`, additional features such as invalidation dependencies and
45+
"Probably early expiration" stampede prevention, you should wrap PSR-16 cache instance with `\Yiisoft\Cache\Cache`:
5246

5347
```php
54-
$cache = new WinCache();
55-
$cache->setSerializer(new JsonSerializer());
48+
$cache = new \Yiisoft\Cache\Cache($arrayCache);
5649
```
5750

58-
Default TTL could be set via `setDefaultTtl()`:
51+
Set a default TTL:
5952

6053
```php
61-
$cache = new ArrayCache();
62-
$cache->setDefaultTtl(60 * 60); // 1 hour
54+
$cache = new \Yiisoft\Cache\Cache($arrayCache, 60 * 60); // 1 hour
6355
```
6456

65-
In order to set key prefix for a cache instance, use `setKeyPrefix()` method:
57+
Set a key prefix:
6658

6759
```php
68-
$cache = new Memcached();
69-
$cache->setKeyPrefix('myapp');
60+
$cache = new \Yiisoft\Cache\Cache($arrayCache, null, 'myapp');
7061
```
7162

72-
## Usage
63+
## General usage
7364

74-
Typical cache usage is the following:
65+
Typical PSR-16 cache usage is the following:
7566

7667
```php
68+
$cache = new \Yiisoft\Cache\ArrayCache();
69+
$parameters = ['user_id' => 42];
7770
$key = 'demo';
7871

7972
// try retrieving $data from cache
@@ -93,6 +86,8 @@ In order to delete value you can use:
9386

9487
```php
9588
$cache->delete($key);
89+
// Or all cache
90+
$cache->clear();
9691
```
9792

9893
To work with values in a more efficient manner, batch operations should be used:
@@ -105,59 +100,88 @@ When using extended cache i.e. PSR-16 cache wrapped with `\Yiisoft\Cache\Cache`,
105100
is less repetitive:
106101

107102
```php
108-
$parameters = ['user_id' => 42];
109-
$data = $cache->getOrSet($key, function () use ($parameters) {
110-
return $this->calculateSomething($parameters);
103+
$cache = new \Yiisoft\Cache\Cache(new \Yiisoft\Cache\ArrayCache());
104+
$key = ['top-products', $count = 10];
105+
106+
$data = $cache->getOrSet($key, function (\Psr\SimpleCache\CacheInterface $cache) use ($count) {
107+
return getTopProductsFromDatabase($count);
111108
}, 3600);
112109
```
113110

114-
Additionally, `add()` and `addMultiple()` are avaialble. These methods work like `set()` and `setMultiple()` except
115-
they store cache only if there is no existing value.
111+
In order to delete value you can use:
112+
113+
```php
114+
$cache->remove($key);
115+
```
116116

117117
### Invalidation dependencies
118118

119-
When using extended cache i.e. PSR-16 cache wrapped with `\Yiisoft\Cache\Cache`, additionally to TTL for `set()`,
120-
`setMultiple()`, `add()`, `addMultiple()` or `getOrSet()` methods you can specify a dependency that may trigger cache
121-
invalidation. Below is an example using tag dependency:
119+
When using `\Yiisoft\Cache\Cache`, additionally to TTL for `getOrSet()` method you can specify a dependency
120+
that may trigger cache invalidation. Below is an example using tag dependency:
122121

123122
```php
123+
/**
124+
* @var callable $callable
125+
* @var \Yiisoft\Cache\CacheInterface $cache
126+
*/
127+
128+
use Yiisoft\Cache\Dependency\TagDependency;
129+
124130
// set multiple cache values marking both with a tag
125-
$cache->set('item_42_price', 13, null, new TagDependency('item_42'));
126-
$cache->set('item_42_total', 26, null, new TagDependency('item_42'));
131+
$cache->getOrSet('item_42_price', $callable, null, new TagDependency('item_42'));
132+
$cache->set('item_42_total', $callable, 3600, new TagDependency('item_42'));
127133

128134
// trigger invalidation by tag
129135
TagDependency::invalidate($cache, 'item_42');
130136
```
131137

132-
Out of there is file dependency that invalidates cache based on file modification time and callback dependency that
133-
invalidates cache when callback result changes.
138+
There is `Yiisoft\Cache\Dependency\FileDependency` that invalidates cache based on file modification time
139+
and `Yiisoft\Cache\Dependency\CallbackDependency` that invalidates cache when callback result changes.
134140

135141
In order to implement your own dependency extend from `Yiisoft\Cache\Dependency\Dependency`.
136142

137-
You may combine multiple dependencies using `AnyDependency` or `AllDependencies`.
143+
You may combine multiple dependencies using `Yiisoft\Cache\Dependency\AnyDependency`
144+
or `Yiisoft\Cache\Dependency\AllDependencies`.
138145

139146

140-
## Implementing your own cache backend
147+
### Cache stampede prevention
148+
149+
[A cache stampede](https://en.wikipedia.org/wiki/Cache_stampede) is a type of cascading failure that can occur when massively parallel computing systems with caching mechanisms come under very high load. This behaviour is sometimes also called dog-piling. The `\Yiisoft\Cache\Cache` uses a built-in "Probably early expiration" algorithm that prevents cache stampede.
150+
This algorithm randomly fakes a cache miss for one user while others are still served the cached value.
151+
You can control its behavior with the fifth optional parameter of `getOrSet()`, which is a float value called `$beta`.
152+
By default, beta is `1.0`, which is sufficient in most cases. The higher the velue the earlier cache will be re-created.
153+
154+
```php
155+
/**
156+
* @var mixed $key
157+
* @var callable $callable
158+
* @var \DateInterval $ttl
159+
* @var \Yiisoft\Cache\CacheInterface $cache
160+
* @var \Yiisoft\Cache\Dependency\Dependency $dependency
161+
*/
162+
163+
$beta = 2.0;
164+
$cache->getOrSet($key, $callable, $ttl, $dependency, $beta);
165+
```
166+
167+
### Cache handlers
141168

142-
There are two ways to implement cache backend. You can start from scratch by implementing `\Psr\SimpleCache\CacheInterface`
143-
or you can inherit from `\Yiisoft\Cache\SimpleCache`. In the latter case you have to implement the following methods:
169+
> Below the handler refers to the implementations of [PSR-16](https://www.php-fig.org/psr/psr-16/).
144170
145-
- `hasValue()` - check if value with a key exists in cache.
146-
- `getValue()` - retrieve the value with a key (if any) from cache
147-
- `setValue()` - store the value with a key into cache
148-
- `deleteValue()` - delete the value with the specified key from cache
149-
- `clear()` - delete all values from cache
171+
This package contains two handlers:
150172

151-
Additionally, you may override the following methods in case backend supports getting any/or setting multiple keys
152-
at once:
173+
- `Yiisoft\Cache\ArrayCache` - provides caching for the current request only by storing the values in an array.
174+
- `Yiisoft\Cache\NullCache` - does not cache anything reporting success for all methods calls.
153175

154-
- `getValues()` - retrieve multiple values from cache
155-
- `setValues()` - store multiple values into cache
156-
- `deleteValues()` - delete multiple values from cache
176+
Extra cache handlers are implemented as separate packages:
157177

158-
## Unit testing
178+
- [APCu](https://github.com/yiisoft/cache-apcu)
179+
- [Database](https://github.com/yiisoft/cache-db)
180+
- [File](https://github.com/yiisoft/cache-file)
181+
- [Memcached](https://github.com/yiisoft/cache-memcached)
182+
- [Wincache](https://github.com/yiisoft/cache-wincache)
159183

160-
The package is tested with [PHPUnit](https://phpunit.de/). To run tests:
184+
### Unit testing
161185

162186
```php
163187
./vendor/bin/phpunit
@@ -171,7 +195,7 @@ The package tests are checked with [Infection](https://infection.github.io/) mut
171195
./vendor/bin/infection
172196
```
173197

174-
## Static analysis
198+
### Static analysis
175199

176200
The code is statically analyzed with [Psalm](https://psalm.dev/). To run static analysis:
177201

0 commit comments

Comments
 (0)