Skip to content

Commit 5ae8a80

Browse files
iainconnordbu
authored andcommitted
Adding cache listener (#48)
1 parent c573ac6 commit 5ae8a80

File tree

5 files changed

+116
-7
lines changed

5 files changed

+116
-7
lines changed

Diff for: CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Change Log
22

3+
## 1.6.0 - 2017-01-29
4+
5+
### Added
6+
7+
* Added `cache_listeners` option, which takes an array of `CacheListener`s, who get notified and can optionally act on a Response based on a cache hit or miss event. An implementation, `AddHeaderCacheListener`, is provided which will add an `X-Cache` header to the response with this information.
8+
39
## 1.5.0 - 2017-11-29
410

511
### Added

Diff for: composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
},
3838
"extra": {
3939
"branch-alias": {
40-
"dev-master": "1.5-dev"
40+
"dev-master": "1.6-dev"
4141
}
4242
}
4343
}

Diff for: src/Cache/Listener/AddHeaderCacheListener.php

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
namespace Http\Client\Common\Plugin\Cache\Listener;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Psr\Cache\CacheItemInterface;
8+
9+
/**
10+
* Adds a header indicating if the response came from cache.
11+
*
12+
* @author Iain Connor <[email protected]>
13+
*/
14+
class AddHeaderCacheListener implements CacheListener
15+
{
16+
/** @var string */
17+
private $headerName;
18+
19+
/**
20+
* @param string $headerName
21+
*/
22+
public function __construct($headerName = 'X-Cache')
23+
{
24+
$this->headerName = $headerName;
25+
}
26+
27+
/**
28+
* Called before the cache plugin returns the response, with information on whether that response came from cache.
29+
*
30+
* @param RequestInterface $request
31+
* @param ResponseInterface $response
32+
* @param bool $fromCache Whether the `$response` was from the cache or not.
33+
* Note that checking `$cacheItem->isHit()` is not sufficent to determine this.
34+
* @param CacheItemInterface|null $cacheItem
35+
*
36+
* @return ResponseInterface
37+
*/
38+
public function onCacheResponse(RequestInterface $request, ResponseInterface $response, $fromCache, $cacheItem)
39+
{
40+
return $response->withHeader($this->headerName, $fromCache ? 'HIT' : 'MISS');
41+
}
42+
}

Diff for: src/Cache/Listener/CacheListener.php

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Http\Client\Common\Plugin\Cache\Listener;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
use Psr\Http\Message\ResponseInterface;
7+
use Psr\Cache\CacheItemInterface;
8+
9+
/**
10+
* Called by the cache plugin with information on the cache status.
11+
* Provides an opportunity to update the response based on whether the cache was a hit or a miss, or
12+
* other cache-meta-data.
13+
*
14+
* @author Iain Connor <[email protected]>
15+
*/
16+
interface CacheListener
17+
{
18+
/**
19+
* Called before the cache plugin returns the response, with information on whether that response came from cache.
20+
*
21+
* @param RequestInterface $request
22+
* @param ResponseInterface $response
23+
* @param bool $fromCache Whether the `$response` was from the cache or not.
24+
* Note that checking `$cacheItem->isHit()` is not sufficent to determine this.
25+
* @param CacheItemInterface|null $cacheItem
26+
*
27+
* @return ResponseInterface
28+
*/
29+
public function onCacheResponse(RequestInterface $request, ResponseInterface $response, $fromCache, $cacheItem);
30+
}

Diff for: src/CachePlugin.php

+37-6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Http\Client\Common\Plugin\Exception\RewindStreamException;
77
use Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator;
88
use Http\Client\Common\Plugin\Cache\Generator\SimpleGenerator;
9+
use Http\Client\Common\Plugin\Cache\Listener\CacheListener;
910
use Http\Message\StreamFactory;
1011
use Http\Promise\FulfilledPromise;
1112
use Psr\Cache\CacheItemInterface;
@@ -59,6 +60,8 @@ final class CachePlugin implements Plugin
5960
* @var array $methods list of request methods which can be cached
6061
* @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses
6162
* @var CacheKeyGenerator $cache_key_generator an object to generate the cache key. Defaults to a new instance of SimpleGenerator
63+
* @var CacheListener[] $cache_listeners an array of objects to act on the response based on the results of the cache check.
64+
* Defaults to an empty array
6265
* }
6366
*/
6467
public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = [])
@@ -129,7 +132,11 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
129132
$method = strtoupper($request->getMethod());
130133
// if the request not is cachable, move to $next
131134
if (!in_array($method, $this->config['methods'])) {
132-
return $next($request);
135+
return $next($request)->then(function (ResponseInterface $response) use ($request) {
136+
$response = $this->handleCacheListeners($request, $response, false, null);
137+
138+
return $response;
139+
});
133140
}
134141

135142
// If we can cache the request
@@ -141,7 +148,10 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
141148
// The array_key_exists() is to be removed in 2.0.
142149
if (array_key_exists('expiresAt', $data) && (null === $data['expiresAt'] || time() < $data['expiresAt'])) {
143150
// This item is still valid according to previous cache headers
144-
return new FulfilledPromise($this->createResponseFromCacheItem($cacheItem));
151+
$response = $this->createResponseFromCacheItem($cacheItem);
152+
$response = $this->handleCacheListeners($request, $response, true, $cacheItem);
153+
154+
return new FulfilledPromise($response);
145155
}
146156

147157
// Add headers to ask the server if this cache is still valid
@@ -154,14 +164,14 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
154164
}
155165
}
156166

157-
return $next($request)->then(function (ResponseInterface $response) use ($cacheItem) {
167+
return $next($request)->then(function (ResponseInterface $response) use ($request, $cacheItem) {
158168
if (304 === $response->getStatusCode()) {
159169
if (!$cacheItem->isHit()) {
160170
/*
161171
* We do not have the item in cache. This plugin did not add If-Modified-Since
162172
* or If-None-Match headers. Return the response from server.
163173
*/
164-
return $response;
174+
return $this->handleCacheListeners($request, $response, false, $cacheItem);
165175
}
166176

167177
// The cached response we have is still valid
@@ -171,7 +181,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
171181
$cacheItem->set($data)->expiresAfter($this->calculateCacheItemExpiresAfter($maxAge));
172182
$this->pool->save($cacheItem);
173183

174-
return $this->createResponseFromCacheItem($cacheItem);
184+
return $this->handleCacheListeners($request, $this->createResponseFromCacheItem($cacheItem), true, $cacheItem);
175185
}
176186

177187
if ($this->isCacheable($response)) {
@@ -196,7 +206,7 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
196206
$this->pool->save($cacheItem);
197207
}
198208

199-
return $response;
209+
return $this->handleCacheListeners($request, $response, false, isset($cacheItem) ? $cacheItem : null);
200210
});
201211
}
202212

@@ -343,6 +353,7 @@ private function configureOptions(OptionsResolver $resolver)
343353
'methods' => ['GET', 'HEAD'],
344354
'respect_response_cache_directives' => ['no-cache', 'private', 'max-age', 'no-store'],
345355
'cache_key_generator' => null,
356+
'cache_listeners' => [],
346357
]);
347358

348359
$resolver->setAllowedTypes('cache_lifetime', ['int', 'null']);
@@ -357,6 +368,7 @@ private function configureOptions(OptionsResolver $resolver)
357368

358369
return empty($matches);
359370
});
371+
$resolver->setAllowedTypes('cache_listeners', ['array']);
360372

361373
$resolver->setNormalizer('respect_cache_headers', function (Options $options, $value) {
362374
if (null !== $value) {
@@ -441,4 +453,23 @@ private function getETag(CacheItemInterface $cacheItem)
441453
}
442454
}
443455
}
456+
457+
/**
458+
* Call the cache listeners, if they are set.
459+
*
460+
* @param RequestInterface $request
461+
* @param ResponseInterface $response
462+
* @param bool $cacheHit
463+
* @param CacheItemInterface|null $cacheItem
464+
*
465+
* @return ResponseInterface
466+
*/
467+
private function handleCacheListeners(RequestInterface $request, ResponseInterface $response, $cacheHit, $cacheItem)
468+
{
469+
foreach ($this->config['cache_listeners'] as $cacheListener) {
470+
$response = $cacheListener->onCacheResponse($request, $response, $cacheHit, $cacheItem);
471+
}
472+
473+
return $response;
474+
}
444475
}

0 commit comments

Comments
 (0)