Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 12 additions & 11 deletions doc/proxy-clients.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,19 @@ Supported invalidation methods
Not all clients support all :ref:`invalidation methods <invalidation methods>`.
This table provides of methods supported by each proxy client:

============= ======= ======= ======= ======= =======
Client Purge Refresh Ban Tagging Clear
============= ======= ======= ======= ======= =======
Varnish ✓ ✓ ✓ ✓
Fastly ✓ ✓ ✓ ✓
============= ======= ======= ======= ======= ========= =======
Client Purge Refresh Ban Tagging Prefix(*) Clear
============= ======= ======= ======= ======= ========= =======
Varnish ✓ ✓ ✓ ✓
Fastly ✓ ✓ ✓
NGINX ✓ ✓
Symfony Cache ✓ ✓ ✓ (1) ✓ (1)
Cloudflare ✓ ✓ (2) ✓
Noop ✓ ✓ ✓ ✓ ✓
Multiplexer ✓ ✓ ✓ ✓ ✓
============= ======= ======= ======= ======= =======
Symfony Cache ✓ ✓ ✓ (1) ✓ (1)
Cloudflare ✓ ✓ (2) ✓ (2) ✓
Noop ✓ ✓ ✓ ✓ ✓
Multiplexer ✓ ✓ ✓ ✓ ✓
============= ======= ======= ======= ======= ========= =======

| (*): A limited version of Ban, that allows to invalidate by the beginning of a path
| (1): Only when using `Toflar Psr6Store`_.
| (2): Only available with `Cloudflare Enterprise`_.

Expand Down Expand Up @@ -357,7 +358,7 @@ the HttpDispatcher is not available for Cloudflare)::
Cloudflare supports different cache purge methods depending on your account.
All Cloudflare accounts support purging the cache by URL and clearing all
cache items. You need a `Cloudflare Enterprise`_ account to purge by cache
tags.
tags or prefixes.

Zone identifier
^^^^^^^^^^^^^^^
Expand Down
15 changes: 15 additions & 0 deletions src/CacheInvalidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use FOS\HttpCache\Exception\UnsupportedProxyOperationException;
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
Expand Down Expand Up @@ -216,6 +217,20 @@ public function invalidateTags(array $tags): static
return $this;
}

public function invalidatePrefixes(array $prefixes): static
{
if (!$this->cache instanceof PrefixCapable) {
throw UnsupportedProxyOperationException::cacheDoesNotImplement('Prefixes');
}
if (!$prefixes) {
return $this;
}

$this->cache->invalidatePrefixes($prefixes);

return $this;
}

/**
* Invalidate URLs based on a regular expression for the URI, an optional
* content type and optional limit to certain hosts.
Expand Down
29 changes: 28 additions & 1 deletion src/ProxyClient/Cloudflare.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
namespace FOS\HttpCache\ProxyClient;

use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
use Psr\Http\Message\RequestFactoryInterface;
Expand All @@ -27,7 +28,7 @@
*
* @author Simon Jones <simon@studio24.net>
*/
class Cloudflare extends HttpProxyClient implements ClearCapable, PurgeCapable, TagCapable
class Cloudflare extends HttpProxyClient implements ClearCapable, PrefixCapable, PurgeCapable, TagCapable
{
/**
* @see https://api.cloudflare.com/#getting-started-endpoints
Expand Down Expand Up @@ -87,6 +88,32 @@ public function invalidateTags(array $tags): static
return $this;
}

/**
* {@inheritdoc}
*
* URL prefix only available with Cloudflare enterprise plans.
*
* The prefixes need to include the domain name, but not the protocol, e.g. "www.example.com/path"
*
* @see https://developers.cloudflare.com/api/resources/cache/methods/purge/
*/
public function invalidatePrefixes(array $prefixes): static
{
if (!$prefixes) {
return $this;
}

$this->queueRequest(
'POST',
sprintf(self::API_ENDPOINT.'/zones/%s/purge_cache', $this->options['zone_identifier']),
[],
false,
json_encode(['prefixes' => $prefixes], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
);

return $this;
}

/**
* @see https://api.cloudflare.com/#zone-purge-files-by-url
* @see https://developers.cloudflare.com/cache/how-to/purge-cache#purge-by-single-file-by-url For details on headers you can pass to clear the cache correctly
Expand Down
28 changes: 28 additions & 0 deletions src/ProxyClient/Invalidation/PrefixCapable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/*
* This file is part of the FOSHttpCache package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FOS\HttpCache\ProxyClient\Invalidation;

use FOS\HttpCache\ProxyClient\ProxyClient;

/**
* An HTTP cache that supports invalidation by a prefix, that is, removing
* or expiring objects from the cache starting with the given string or strings.
*/
interface PrefixCapable extends ProxyClient
{
/**
* Remove/Expire cache objects based on URL prefixes.
*
* @param string[] $prefixes Prefixed objects that should be removed/expired from the cache. An empty prefix list should be ignored.
*/
public function invalidatePrefixes(array $prefixes): static;
}
18 changes: 17 additions & 1 deletion src/ProxyClient/MultiplexerClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use FOS\HttpCache\Exception\InvalidArgumentException;
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
Expand All @@ -23,7 +24,7 @@
*
* @author Emanuele Panzeri <thepanz@gmail.com>
*/
class MultiplexerClient implements BanCapable, PurgeCapable, RefreshCapable, TagCapable, ClearCapable
class MultiplexerClient implements BanCapable, PrefixCapable, PurgeCapable, RefreshCapable, TagCapable, ClearCapable
{
/**
* @var ProxyClient[]
Expand Down Expand Up @@ -93,6 +94,21 @@ public function invalidateTags(array $tags): static
return $this;
}

/**
* Forwards prefix invalidation request to all clients.
*
* {@inheritdoc}
*/
public function invalidatePrefixes(array $prefixes): static
{
if (!$prefixes) {
return $this;
}
$this->invoke(PrefixCapable::class, 'invalidatePrefixes', [$prefixes]);

return $this;
}

/**
* Forwards to all clients.
*
Expand Down
8 changes: 7 additions & 1 deletion src/ProxyClient/Noop.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
Expand All @@ -26,7 +27,7 @@
*
* @author Gavin Staniforth <gavin@gsdev.me>
*/
class Noop implements ProxyClient, BanCapable, PurgeCapable, RefreshCapable, TagCapable, ClearCapable
class Noop implements ProxyClient, BanCapable, PrefixCapable, PurgeCapable, RefreshCapable, TagCapable, ClearCapable
{
public function ban(array $headers): static
{
Expand All @@ -43,6 +44,11 @@ public function invalidateTags(array $tags): static
return $this;
}

public function invalidatePrefixes(array $prefixes): static
{
return $this;
}

public function purge(string $url, array $headers = []): static
{
return $this;
Expand Down
19 changes: 18 additions & 1 deletion src/ProxyClient/Varnish.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use FOS\HttpCache\Exception\InvalidArgumentException;
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
Expand All @@ -36,7 +37,7 @@
*
* @author David de Boer <david@driebit.nl>
*/
class Varnish extends HttpProxyClient implements BanCapable, PurgeCapable, RefreshCapable, TagCapable
class Varnish extends HttpProxyClient implements BanCapable, PrefixCapable, PurgeCapable, RefreshCapable, TagCapable
{
public const HTTP_METHOD_BAN = 'BAN';

Expand Down Expand Up @@ -127,6 +128,22 @@ public function banPath(string $path, ?string $contentType = null, array|string|
return $this->ban($headers);
}

public function invalidatePrefixes(array $prefixes): static
{
if (!$prefixes) {
return $this;
}

foreach ($prefixes as $prefix) {
$parts = explode('/', $prefix, 2);
$host = $parts[0];
$path = isset($parts[1]) ? '/'.$parts[1] : '/';
$this->banPath($path, null, $host);
}

return $this;
}

public function purge(string $url, array $headers = []): static
{
$this->queueRequest(self::HTTP_METHOD_PURGE, $url, $headers);
Expand Down
22 changes: 22 additions & 0 deletions tests/Unit/ProxyClient/CloudflareTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@ function (RequestInterface $request) {
$cloudflare->invalidateTags(['tag-one', 'tag-two']);
}

public function testInvalidatePrefixesPurge(): void
{
$cloudflare = $this->getProxyClient();

$this->httpDispatcher->shouldReceive('invalidate')->once()->with(
\Mockery::on(
function (RequestInterface $request) {
$this->assertEquals('POST', $request->getMethod());
$this->assertEquals('Bearer '.self::AUTH_TOKEN, current($request->getHeader('Authorization')));
$this->assertEquals(sprintf('/client/v4/zones/%s/purge_cache', self::ZONE_IDENTIFIER), $request->getRequestTarget());

$this->assertEquals('{"prefixes":["example.com/one/","example.com/two/"]}', $request->getBody()->getContents());

return true;
}
),
false
);

$cloudflare->invalidatePrefixes(['example.com/one/', 'example.com/two/']);
}

public function testPurge(): void
{
$cloudflare = $this->getProxyClient();
Expand Down
16 changes: 16 additions & 0 deletions tests/Unit/ProxyClient/MultiplexerClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use FOS\HttpCache\Exception\InvalidArgumentException;
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
Expand Down Expand Up @@ -103,6 +104,21 @@ public function testInvalidateTags(): void
$this->assertSame($multiplexer, $multiplexer->invalidateTags($tags));
}

public function testInvalidatePrefixes(): void
{
$prefixes = ['example.com/one/', 'example.com/two/'];

$mockClient = \Mockery::mock(PrefixCapable::class)
->shouldReceive('invalidatePrefixes')
->once()
->with($prefixes)
->getMock();

$multiplexer = new MultiplexerClient([$mockClient]);

$this->assertSame($multiplexer, $multiplexer->invalidatePrefixes($prefixes));
}

public function testRefresh(): void
{
$url = 'example.com';
Expand Down
5 changes: 5 additions & 0 deletions tests/Unit/ProxyClient/NoopTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public function testInvalidateTags(): void
$this->assertSame($this->noop, $this->noop->invalidateTags(['tag123']));
}

public function testInvalidatePrefixes(): void
{
$this->assertSame($this->noop, $this->noop->invalidatePrefixes(['example.com/one/']));
}

public function testBanPath(): void
{
$this->assertSame($this->noop, $this->noop->banPath('/123'));
Expand Down
19 changes: 19 additions & 0 deletions tests/Unit/ProxyClient/VarnishTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,4 +236,23 @@ function (RequestInterface $request) {

$varnish->refresh('/fresh');
}

public function testInvalidatePrefixes(): void
{
$varnish = new Varnish($this->httpDispatcher);
$this->httpDispatcher->shouldReceive('invalidate')->once()->with(
\Mockery::on(
function (RequestInterface $request) {
$this->assertEquals('BAN', $request->getMethod());
$this->assertEquals('example.org', $request->getHeaderLine('X-Host'));
$this->assertEquals('/one/', $request->getHeaderLine('X-Url'));
$this->assertEquals('.*', $request->getHeaderLine('X-Content-Type'));

return true;
}
),
false
);
$varnish->invalidatePrefixes(['example.org/one/']);
}
}
Loading