Skip to content

Commit

Permalink
Also put entries into the cache that allow stale-while-revalidate (#125)
Browse files Browse the repository at this point in the history
* Also put entries into the cache that allow stale-while-revalidate

* Rename phpunit.xml to phpunit.xml.dist

* Add require-dev dependency on symfony/phpunit-bridge to allow for clock mocking

See https://symfony.com/doc/current/components/phpunit_bridge.html#time-sensitive-tests.

This helps to get reliable results when time computations are under test.

* Explain in the DocBlock what exactly the computed TTL means

* Add first tests for CacheEntry

* Improve the docblock explanation

* Allow symfony/phpunit-bridge 4.4 as well

... so we get compatibility even with PHPUnit 4.

* Ensure BC with PHPUnit 4

* Avoid failing tests due to deprecation notices
  • Loading branch information
mpdude authored Feb 14, 2020
1 parent 8f18549 commit f978b8d
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 6 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"psr/cache": "^1.0",
"cache/array-adapter": "^0.4 || ^0.5 || ^1.0",
"illuminate/cache": "^5.0",
"cache/simple-cache-bridge": "^0.1 || ^1.0"
"cache/simple-cache-bridge": "^0.1 || ^1.0",
"symfony/phpunit-bridge": "^4.4 || ^5.0"
},
"autoload": {
"psr-4": {
Expand Down
7 changes: 7 additions & 0 deletions phpunit.xml → phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
colors="true"
bootstrap="./vendor/autoload.php"
>
<php>
<env name="SYMFONY_DEPRECATIONS_HELPER" value="max[self]=0" />
</php>

<testsuites>
<testsuite name="Project Test Suite">
Expand All @@ -21,4 +24,8 @@
<log type="coverage-clover" target="coverage.xml"/>
</logging>

<listeners>
<listener class="\Symfony\Bridge\PhpUnit\SymfonyTestsListener"/>
</listeners>

</phpunit>
23 changes: 18 additions & 5 deletions src/CacheEntry.php
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,12 @@ public function hasValidationInformation()
}

/**
* Time in seconds how long the entry should be kept in the cache
*
* This will not give the time (in seconds) that the response will still be fresh for
* from the HTTP point of view, but an upper bound on how long it is necessary and
* reasonable to keep the response in a cache (to re-use it or re-validate it later on).
*
* @return int TTL in seconds (0 = infinite)
*/
public function getTTL()
Expand All @@ -230,14 +236,21 @@ public function getTTL()
return 0;
}

$ttl = 0;

// Keep it when stale if error
if ($this->staleIfErrorTo !== null) {
// Keep it when stale if error
$ttl = $this->staleIfErrorTo->getTimestamp() - time();
} else {
// Keep it until it become stale
$ttl = $this->staleAt->getTimestamp() - time();
$ttl = max($ttl, $this->staleIfErrorTo->getTimestamp() - time());
}

// Keep it when stale-while-revalidate
if ($this->staleWhileRevalidateTo !== null) {
$ttl = max($ttl, $this->staleWhileRevalidateTo->getTimestamp() - time());
}

// Keep it until it become stale
$ttl = max($ttl, $this->staleAt->getTimestamp() - time());

// Don't return 0, it's reserved for infinite TTL
return $ttl !== 0 ? (int) $ttl : -1;
}
Expand Down
92 changes: 92 additions & 0 deletions tests/CacheEntryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Kevinrob\GuzzleCache\Tests;

use Kevinrob\GuzzleCache\CacheEntry;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

/**
* @group time-sensitive
*/
class CacheEntryTest extends \PHPUnit_Framework_TestCase
{
/**
* @var RequestInterface
*/
private $request;

/**
* @var ResponseInterface
*/
private $response;

private $responseHeaders = [];

protected function setUp()
{
parent::setUp();
$this->request = $this->getMockBuilder(RequestInterface::class)->getMock();
$this->response = $this->getMockBuilder(ResponseInterface::class)->getMock();
$this->response->method('getHeader')->will($this->returnCallback(function ($header) {
if (isset($this->responseHeaders[$header])) {
return $this->responseHeaders[$header];
}

return [];
}));
$this->response->method('hasHeader')->will($this->returnCallback(function ($header) {
return isset($this->responseHeaders[$header]);
}));
}

public function testTtlForValidateableResponseShouldBeInfinite()
{
$this->setResponseHeader('Etag', 'some-etag');
$cacheEntry = new CacheEntry($this->request, $this->response, $this->makeDateTimeOffset());

// getTTL() will return 0 to indicate "infinite"
$this->assertEquals(0, $cacheEntry->getTTL());
}

public function testTtlForSimpleExpiration()
{
$cacheEntry = new CacheEntry($this->request, $this->response, $this->makeDateTimeOffset(10));

$this->assertEquals(10, $cacheEntry->getTTL());
}

public function testTtlConsidersStaleIfError()
{
$this->setResponseHeader('Cache-Control', 'stale-if-error=30');
$cacheEntry = new CacheEntry($this->request, $this->response, $this->makeDateTimeOffset(10));

$this->assertEquals(40, $cacheEntry->getTTL());
}

public function testTtlConsidersStaleWhileRevalidate()
{
$this->setResponseHeader('Cache-Control', 'stale-while-revalidate=30');
$cacheEntry = new CacheEntry($this->request, $this->response, $this->makeDateTimeOffset(10));

$this->assertEquals(40, $cacheEntry->getTTL());
}

public function testTtlUsesMaximumPossibleLifetime()
{
$this->setResponseHeader('Cache-Control', 'stale-while-revalidate=30, stale-if-error=60');
$cacheEntry = new CacheEntry($this->request, $this->response, $this->makeDateTimeOffset(10));

$this->assertEquals(70, $cacheEntry->getTTL());
}

private function setResponseHeader($name, $value)
{
$this->responseHeaders[$name] = [$value];
}

private function makeDateTimeOffset($offset = 0)
{
return \DateTime::createFromFormat('U', time() + $offset);
}
}

0 comments on commit f978b8d

Please sign in to comment.