diff --git a/src/CacheMiddleware.php b/src/CacheMiddleware.php index 0f6ea2a..a37386e 100644 --- a/src/CacheMiddleware.php +++ b/src/CacheMiddleware.php @@ -49,6 +49,15 @@ class CacheMiddleware */ protected $httpMethods = ['GET' => true]; + /** + * List of safe methods + * + * https://datatracker.ietf.org/doc/html/rfc7231#section-4.2.1 + * + * @var array + */ + protected $safeMethods = ['GET' => true, 'HEAD' => true, 'OPTIONS' => true, 'TRACE' => true]; + /** * @param CacheStrategyInterface|null $cacheStrategy */ @@ -117,8 +126,10 @@ public function __invoke(callable $handler) return $handler($request, $options)->then( function (ResponseInterface $response) use ($request) { - // Invalidate cache after a call of non-safe method on the same URI - $response = $this->invalidateCache($request, $response); + if (!isset($this->safeMethods[$request->getMethod()])) { + // Invalidate cache after a call of non-safe method on the same URI + $response = $this->invalidateCache($request, $response); + } return $response->withHeader(self::HEADER_CACHE_INFO, self::HEADER_CACHE_MISS); } @@ -375,7 +386,9 @@ public static function getMiddleware(CacheStrategyInterface $cacheStorage = null */ private function invalidateCache(RequestInterface $request, ResponseInterface $response) { - $this->cacheStorage->delete($request); + foreach (array_keys($this->httpMethods) as $method) { + $this->cacheStorage->delete($request->withMethod($method)); + } return $response->withHeader(self::HEADER_INVALIDATION, true); } diff --git a/tests/InvalidateCacheTest.php b/tests/InvalidateCacheTest.php index 50cf991..d7b2fdf 100644 --- a/tests/InvalidateCacheTest.php +++ b/tests/InvalidateCacheTest.php @@ -2,11 +2,14 @@ namespace Kevinrob\GuzzleCache\Tests; +use Cache\Adapter\PHPArray\ArrayCachePool; use GuzzleHttp\Client; use GuzzleHttp\HandlerStack; use GuzzleHttp\Promise\FulfilledPromise; use GuzzleHttp\Psr7\Response; use Kevinrob\GuzzleCache\CacheMiddleware; +use Kevinrob\GuzzleCache\Storage\Psr6CacheStorage; +use Kevinrob\GuzzleCache\Strategy\PrivateCacheStrategy; use PHPUnit\Framework\TestCase; class InvalidateCacheTest extends TestCase @@ -16,35 +19,81 @@ class InvalidateCacheTest extends TestCase */ protected $client; + /** + * @var CacheMiddleware + */ + protected $middleware; + protected function setUp(): void { - // Create default HandlerStack $stack = HandlerStack::create(function () { - return new FulfilledPromise(new Response()); + return new FulfilledPromise(new Response(200, [ + 'Cache-Control' => 'private, max-age=300' + ])); }); - // Add this middleware to the top with `push` - $stack->push(new CacheMiddleware(), 'cache'); + $this->middleware = new CacheMiddleware(new PrivateCacheStrategy( + new Psr6CacheStorage(new ArrayCachePool()) + )); + + $stack->push($this->middleware, 'cache'); - // Initialize the client with the handler option $this->client = new Client(['handler' => $stack]); } - public function testInvalidationCacheIfNotValidHttpMethod() + /** + * @dataProvider unsafeMethods + */ + public function testItInvalidatesForUnsafeHttpMethods($unsafeMethod) { - $response = $this->client->get('anything'); - $this->assertSame('', $response->getHeaderLine(CacheMiddleware::HEADER_INVALIDATION)); + $this->middleware->setHttpMethods([ + 'GET' => true, + 'HEAD' => true, + ]); - $response = $this->client->post('anything'); - $this->assertSame('1', $response->getHeaderLine(CacheMiddleware::HEADER_INVALIDATION)); + $this->client->get('resource'); + $this->client->head('resource'); - $response = $this->client->put('anything'); + $response = $this->client->{$unsafeMethod}('resource'); $this->assertSame('1', $response->getHeaderLine(CacheMiddleware::HEADER_INVALIDATION)); - $response = $this->client->delete('anything'); - $this->assertSame('1', $response->getHeaderLine(CacheMiddleware::HEADER_INVALIDATION)); + $response = $this->client->get('resource'); + $this->assertEquals(CacheMiddleware::HEADER_CACHE_MISS, $response->getHeaderLine('X-Kevinrob-Cache')); - $response = $this->client->patch('anything'); - $this->assertSame('1', $response->getHeaderLine(CacheMiddleware::HEADER_INVALIDATION)); + $response = $this->client->head('resource'); + $this->assertEquals(CacheMiddleware::HEADER_CACHE_MISS, $response->getHeaderLine('X-Kevinrob-Cache')); + } + + /** + * @dataProvider safeMethods + */ + public function testItDoesNotInvalidateForSafeHttpMethods($safeMethod) + { + $this->client->get('resource'); + + $response = $this->client->{$safeMethod}('resource'); + $this->assertSame('', $response->getHeaderLine(CacheMiddleware::HEADER_INVALIDATION)); + + $response = $this->client->get('resource'); + $this->assertEquals(CacheMiddleware::HEADER_CACHE_HIT, $response->getHeaderLine('X-Kevinrob-Cache')); + } + + public function unsafeMethods() + { + return [ + 'delete' => ['delete'], + 'put' => ['put'], + 'post' => ['post'], + ]; + } + + public function safemethods() + { + return [ + 'get' => ['get'], + 'options' => ['options'], + 'trace' => ['trace'], + 'head' => ['head'], + ]; } }