Skip to content

Commit c49bab8

Browse files
authored
Added CacheKeyGenerator (#35)
* Added CacheKeyGenerator Also added a default generator * cs fix * Support PSR-4 * Added test to make sure cache_key_generator works * cs * Changes according to feedback. * Make plugin final * Bugfixes * Remove final * typo
1 parent df86eb5 commit c49bab8

File tree

5 files changed

+137
-6
lines changed

5 files changed

+137
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace spec\Http\Client\Common\Plugin\Cache\Generator;
4+
5+
use PhpSpec\ObjectBehavior;
6+
use Psr\Http\Message\RequestInterface;
7+
8+
class SimpleGeneratorSpec extends ObjectBehavior
9+
{
10+
public function it_is_initializable()
11+
{
12+
$this->shouldHaveType('Http\Client\Common\Plugin\Cache\Generator\SimpleGenerator');
13+
}
14+
15+
public function it_is_a_key_generator()
16+
{
17+
$this->shouldImplement('Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator');
18+
}
19+
20+
public function it_generates_cache_from_request(RequestInterface $request)
21+
{
22+
$request->getMethod()->shouldBeCalled()->willReturn('GET');
23+
$request->getUri()->shouldBeCalled()->willReturn('http://example.com/foo');
24+
$request->getBody()->shouldBeCalled()->willReturn('bar');
25+
26+
$this->generate($request)->shouldReturn('GET http://example.com/foo bar');
27+
}
28+
29+
public function it_generates_cache_from_request_with_no_body(RequestInterface $request)
30+
{
31+
$request->getMethod()->shouldBeCalled()->willReturn('GET');
32+
$request->getUri()->shouldBeCalled()->willReturn('http://example.com/foo');
33+
$request->getBody()->shouldBeCalled()->willReturn('');
34+
35+
// No extra space after uri
36+
$this->generate($request)->shouldReturn('GET http://example.com/foo');
37+
}
38+
}

spec/CachePluginSpec.php

+42
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace spec\Http\Client\Common\Plugin;
44

5+
use Http\Client\Common\Plugin\Cache\Generator\SimpleGenerator;
56
use Prophecy\Argument;
67
use Http\Message\StreamFactory;
78
use Http\Promise\FulfilledPromise;
@@ -400,6 +401,47 @@ function it_caches_private_responses_when_allowed(
400401
}
401402

402403

404+
function it_can_be_initialized_with_custom_cache_key_generator(
405+
CacheItemPoolInterface $pool,
406+
CacheItemInterface $item,
407+
StreamFactory $streamFactory,
408+
RequestInterface $request,
409+
ResponseInterface $response,
410+
StreamInterface $stream,
411+
SimpleGenerator $generator
412+
) {
413+
$this->beConstructedThrough('clientCache', [$pool, $streamFactory, [
414+
'cache_key_generator' => $generator,
415+
]]);
416+
417+
$generator->generate($request)->shouldBeCalled()->willReturn('foo');
418+
419+
$stream->isSeekable()->willReturn(true);
420+
$stream->rewind()->shouldBeCalled();
421+
$streamFactory->createStream(Argument::any())->willReturn($stream);
422+
423+
$request->getMethod()->willReturn('GET');
424+
$request->getUri()->willReturn('/');
425+
$response->withBody(Argument::any())->willReturn($response);
426+
427+
$pool->getItem(Argument::any())->shouldBeCalled()->willReturn($item);
428+
$item->isHit()->willReturn(true);
429+
$item->get()->willReturn([
430+
'response' => $response->getWrappedObject(),
431+
'body' => 'body',
432+
'expiresAt' => null,
433+
'createdAt' => 0,
434+
'etag' => []
435+
]);
436+
437+
$next = function (RequestInterface $request) use ($response) {
438+
return new FulfilledPromise($response->getWrappedObject());
439+
};
440+
441+
$this->handleRequest($request, $next, function () {});
442+
}
443+
444+
403445
/**
404446
* Private function to match cache item data.
405447
*
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Http\Client\Common\Plugin\Cache\Generator;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
7+
/**
8+
* An interface for generate a cache key.
9+
*
10+
* @author Tobias Nyholm <[email protected]>
11+
*/
12+
interface CacheKeyGenerator
13+
{
14+
/**
15+
* Generate a cache key from a Request.
16+
*
17+
* @param RequestInterface $request
18+
*
19+
* @return string
20+
*/
21+
public function generate(RequestInterface $request);
22+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Http\Client\Common\Plugin\Cache\Generator;
4+
5+
use Psr\Http\Message\RequestInterface;
6+
7+
/**
8+
* Generate a cache key from the request method, URI and body.
9+
*
10+
* @author Tobias Nyholm <[email protected]>
11+
*/
12+
class SimpleGenerator implements CacheKeyGenerator
13+
{
14+
public function generate(RequestInterface $request)
15+
{
16+
$body = (string) $request->getBody();
17+
if (!empty($body)) {
18+
$body = ' '.$body;
19+
}
20+
21+
return $request->getMethod().' '.$request->getUri().$body;
22+
}
23+
}

src/CachePlugin.php

+12-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use Http\Client\Common\Plugin;
66
use Http\Client\Common\Plugin\Exception\RewindStreamException;
7+
use Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator;
8+
use Http\Client\Common\Plugin\Cache\Generator\SimpleGenerator;
79
use Http\Message\StreamFactory;
810
use Http\Promise\FulfilledPromise;
911
use Psr\Cache\CacheItemInterface;
@@ -55,7 +57,8 @@ final class CachePlugin implements Plugin
5557
* we have to store the cache for a longer time than the server originally says it is valid for.
5658
* We store a cache item for $cache_lifetime + max age of the response.
5759
* @var array $methods list of request methods which can be cached
58-
* @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses.
60+
* @var array $respect_response_cache_directives list of cache directives this plugin will respect while caching responses
61+
* @var CacheKeyGenerator $cache_key_generator a class to generate the cache key. Defaults to SimpleGenerator
5962
* }
6063
*/
6164
public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamFactory, array $config = [])
@@ -73,6 +76,10 @@ public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamF
7376
$optionsResolver = new OptionsResolver();
7477
$this->configureOptions($optionsResolver);
7578
$this->config = $optionsResolver->resolve($config);
79+
80+
if (null === $this->config['cache_key_generator']) {
81+
$this->config['cache_key_generator'] = new SimpleGenerator();
82+
}
7683
}
7784

7885
/**
@@ -282,12 +289,9 @@ private function getCacheControlDirective(ResponseInterface $response, $name)
282289
*/
283290
private function createCacheKey(RequestInterface $request)
284291
{
285-
$body = (string) $request->getBody();
286-
if (!empty($body)) {
287-
$body = ' '.$body;
288-
}
292+
$key = $this->config['cache_key_generator']->generate($request);
289293

290-
return hash($this->config['hash_algo'], $request->getMethod().' '.$request->getUri().$body);
294+
return hash($this->config['hash_algo'], $key);
291295
}
292296

293297
/**
@@ -338,12 +342,14 @@ private function configureOptions(OptionsResolver $resolver)
338342
'hash_algo' => 'sha1',
339343
'methods' => ['GET', 'HEAD'],
340344
'respect_response_cache_directives' => ['no-cache', 'private', 'max-age', 'no-store'],
345+
'cache_key_generator' => null,
341346
]);
342347

343348
$resolver->setAllowedTypes('cache_lifetime', ['int', 'null']);
344349
$resolver->setAllowedTypes('default_ttl', ['int', 'null']);
345350
$resolver->setAllowedTypes('respect_cache_headers', 'bool');
346351
$resolver->setAllowedTypes('methods', 'array');
352+
$resolver->setAllowedTypes('cache_key_generator', ['null', 'Http\Client\Common\Plugin\Cache\Generator\CacheKeyGenerator']);
347353
$resolver->setAllowedValues('hash_algo', hash_algos());
348354
$resolver->setAllowedValues('methods', function ($value) {
349355
/* RFC7230 sections 3.1.1 and 3.2.6 except limited to uppercase characters. */

0 commit comments

Comments
 (0)