Skip to content

Commit 50f4c73

Browse files
committed
Merge branch 'prefix-invalidation' into 3.x
2 parents f97ab23 + e1c5025 commit 50f4c73

12 files changed

Lines changed: 192 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ See also the [GitHub releases page](https://github.com/FriendsOfSymfony/FOSHttpC
66
3.x
77
===
88

9+
3.2.0
10+
-----
11+
12+
* Added support for prefix invalidation, a special case of banning for cloudflare enterprise.
13+
914
3.1.2
1015
-----
1116

doc/proxy-clients.rst

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,19 @@ Supported invalidation methods
1919
Not all clients support all :ref:`invalidation methods <invalidation methods>`.
2020
This table provides of methods supported by each proxy client:
2121

22-
============= ======= ======= ======= ======= =======
23-
Client Purge Refresh Ban Tagging Clear
24-
============= ======= ======= ======= ======= =======
25-
Varnish ✓ ✓ ✓ ✓
26-
Fastly ✓ ✓ ✓ ✓
22+
============= ======= ======= ======= ======= ========= =======
23+
Client Purge Refresh Ban Tagging Prefix(*) Clear
24+
============= ======= ======= ======= ======= ========= =======
25+
Varnish ✓ ✓ ✓ ✓
26+
Fastly ✓ ✓ ✓
2727
NGINX ✓ ✓
28-
Symfony Cache ✓ ✓ ✓ (1) ✓ (1)
29-
Cloudflare ✓ ✓ (2) ✓
30-
Noop ✓ ✓ ✓ ✓ ✓
31-
Multiplexer ✓ ✓ ✓ ✓ ✓
32-
============= ======= ======= ======= ======= =======
28+
Symfony Cache ✓ ✓ ✓ (1) ✓ (1)
29+
Cloudflare ✓ ✓ (2) ✓ (2) ✓
30+
Noop ✓ ✓ ✓ ✓ ✓
31+
Multiplexer ✓ ✓ ✓ ✓ ✓
32+
============= ======= ======= ======= ======= ========= =======
3333

34+
| (*): A limited version of Ban, that allows to invalidate by the beginning of a path
3435
| (1): Only when using `Toflar Psr6Store`_.
3536
| (2): Only available with `Cloudflare Enterprise`_.
3637
@@ -357,7 +358,7 @@ the HttpDispatcher is not available for Cloudflare)::
357358
Cloudflare supports different cache purge methods depending on your account.
358359
All Cloudflare accounts support purging the cache by URL and clearing all
359360
cache items. You need a `Cloudflare Enterprise`_ account to purge by cache
360-
tags.
361+
tags or prefixes.
361362

362363
Zone identifier
363364
^^^^^^^^^^^^^^^

src/CacheInvalidator.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use FOS\HttpCache\Exception\UnsupportedProxyOperationException;
1919
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
2020
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
21+
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
2122
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
2223
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
2324
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
@@ -216,6 +217,20 @@ public function invalidateTags(array $tags): static
216217
return $this;
217218
}
218219

220+
public function invalidatePrefixes(array $prefixes): static
221+
{
222+
if (!$this->cache instanceof PrefixCapable) {
223+
throw UnsupportedProxyOperationException::cacheDoesNotImplement('Prefixes');
224+
}
225+
if (!$prefixes) {
226+
return $this;
227+
}
228+
229+
$this->cache->invalidatePrefixes($prefixes);
230+
231+
return $this;
232+
}
233+
219234
/**
220235
* Invalidate URLs based on a regular expression for the URI, an optional
221236
* content type and optional limit to certain hosts.

src/ProxyClient/Cloudflare.php

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace FOS\HttpCache\ProxyClient;
1313

1414
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
15+
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
1516
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
1617
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
1718
use Psr\Http\Message\RequestFactoryInterface;
@@ -27,7 +28,7 @@
2728
*
2829
* @author Simon Jones <simon@studio24.net>
2930
*/
30-
class Cloudflare extends HttpProxyClient implements ClearCapable, PurgeCapable, TagCapable
31+
class Cloudflare extends HttpProxyClient implements ClearCapable, PrefixCapable, PurgeCapable, TagCapable
3132
{
3233
/**
3334
* @see https://api.cloudflare.com/#getting-started-endpoints
@@ -87,6 +88,32 @@ public function invalidateTags(array $tags): static
8788
return $this;
8889
}
8990

91+
/**
92+
* {@inheritdoc}
93+
*
94+
* URL prefix only available with Cloudflare enterprise plans.
95+
*
96+
* The prefixes need to include the domain name, but not the protocol, e.g. "www.example.com/path"
97+
*
98+
* @see https://developers.cloudflare.com/api/resources/cache/methods/purge/
99+
*/
100+
public function invalidatePrefixes(array $prefixes): static
101+
{
102+
if (!$prefixes) {
103+
return $this;
104+
}
105+
106+
$this->queueRequest(
107+
'POST',
108+
sprintf(self::API_ENDPOINT.'/zones/%s/purge_cache', $this->options['zone_identifier']),
109+
[],
110+
false,
111+
json_encode(['prefixes' => $prefixes], JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)
112+
);
113+
114+
return $this;
115+
}
116+
90117
/**
91118
* @see https://api.cloudflare.com/#zone-purge-files-by-url
92119
* @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
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the FOSHttpCache package.
5+
*
6+
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace FOS\HttpCache\ProxyClient\Invalidation;
13+
14+
use FOS\HttpCache\ProxyClient\ProxyClient;
15+
16+
/**
17+
* An HTTP cache that supports invalidation by a prefix, that is, removing
18+
* or expiring objects from the cache starting with the given string or strings.
19+
*/
20+
interface PrefixCapable extends ProxyClient
21+
{
22+
/**
23+
* Remove/Expire cache objects based on URL prefixes.
24+
*
25+
* @param string[] $prefixes Prefixed objects that should be removed/expired from the cache. An empty prefix list should be ignored.
26+
*/
27+
public function invalidatePrefixes(array $prefixes): static;
28+
}

src/ProxyClient/MultiplexerClient.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use FOS\HttpCache\Exception\InvalidArgumentException;
1515
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
1616
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
17+
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
1718
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
1819
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
1920
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
@@ -23,7 +24,7 @@
2324
*
2425
* @author Emanuele Panzeri <thepanz@gmail.com>
2526
*/
26-
class MultiplexerClient implements BanCapable, PurgeCapable, RefreshCapable, TagCapable, ClearCapable
27+
class MultiplexerClient implements BanCapable, PrefixCapable, PurgeCapable, RefreshCapable, TagCapable, ClearCapable
2728
{
2829
/**
2930
* @var ProxyClient[]
@@ -93,6 +94,21 @@ public function invalidateTags(array $tags): static
9394
return $this;
9495
}
9596

97+
/**
98+
* Forwards prefix invalidation request to all clients.
99+
*
100+
* {@inheritdoc}
101+
*/
102+
public function invalidatePrefixes(array $prefixes): static
103+
{
104+
if (!$prefixes) {
105+
return $this;
106+
}
107+
$this->invoke(PrefixCapable::class, 'invalidatePrefixes', [$prefixes]);
108+
109+
return $this;
110+
}
111+
96112
/**
97113
* Forwards to all clients.
98114
*

src/ProxyClient/Noop.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
1515
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
16+
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
1617
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
1718
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
1819
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
@@ -26,7 +27,7 @@
2627
*
2728
* @author Gavin Staniforth <gavin@gsdev.me>
2829
*/
29-
class Noop implements ProxyClient, BanCapable, PurgeCapable, RefreshCapable, TagCapable, ClearCapable
30+
class Noop implements ProxyClient, BanCapable, PrefixCapable, PurgeCapable, RefreshCapable, TagCapable, ClearCapable
3031
{
3132
public function ban(array $headers): static
3233
{
@@ -43,6 +44,11 @@ public function invalidateTags(array $tags): static
4344
return $this;
4445
}
4546

47+
public function invalidatePrefixes(array $prefixes): static
48+
{
49+
return $this;
50+
}
51+
4652
public function purge(string $url, array $headers = []): static
4753
{
4854
return $this;

src/ProxyClient/Varnish.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use FOS\HttpCache\Exception\InvalidArgumentException;
1515
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
16+
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
1617
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
1718
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
1819
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
@@ -36,7 +37,7 @@
3637
*
3738
* @author David de Boer <david@driebit.nl>
3839
*/
39-
class Varnish extends HttpProxyClient implements BanCapable, PurgeCapable, RefreshCapable, TagCapable
40+
class Varnish extends HttpProxyClient implements BanCapable, PrefixCapable, PurgeCapable, RefreshCapable, TagCapable
4041
{
4142
public const HTTP_METHOD_BAN = 'BAN';
4243

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

131+
public function invalidatePrefixes(array $prefixes): static
132+
{
133+
if (!$prefixes) {
134+
return $this;
135+
}
136+
137+
foreach ($prefixes as $prefix) {
138+
$parts = explode('/', $prefix, 2);
139+
$host = $parts[0];
140+
$path = isset($parts[1]) ? '/'.$parts[1] : '/';
141+
$this->banPath($path, null, $host);
142+
}
143+
144+
return $this;
145+
}
146+
130147
public function purge(string $url, array $headers = []): static
131148
{
132149
$this->queueRequest(self::HTTP_METHOD_PURGE, $url, $headers);

tests/Unit/ProxyClient/CloudflareTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ function (RequestInterface $request) {
7171
$cloudflare->invalidateTags(['tag-one', 'tag-two']);
7272
}
7373

74+
public function testInvalidatePrefixesPurge(): void
75+
{
76+
$cloudflare = $this->getProxyClient();
77+
78+
$this->httpDispatcher->shouldReceive('invalidate')->once()->with(
79+
\Mockery::on(
80+
function (RequestInterface $request) {
81+
$this->assertEquals('POST', $request->getMethod());
82+
$this->assertEquals('Bearer '.self::AUTH_TOKEN, current($request->getHeader('Authorization')));
83+
$this->assertEquals(sprintf('/client/v4/zones/%s/purge_cache', self::ZONE_IDENTIFIER), $request->getRequestTarget());
84+
85+
$this->assertEquals('{"prefixes":["example.com/one/","example.com/two/"]}', $request->getBody()->getContents());
86+
87+
return true;
88+
}
89+
),
90+
false
91+
);
92+
93+
$cloudflare->invalidatePrefixes(['example.com/one/', 'example.com/two/']);
94+
}
95+
7496
public function testPurge(): void
7597
{
7698
$cloudflare = $this->getProxyClient();

tests/Unit/ProxyClient/MultiplexerClientTest.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use FOS\HttpCache\Exception\InvalidArgumentException;
1515
use FOS\HttpCache\ProxyClient\Invalidation\BanCapable;
1616
use FOS\HttpCache\ProxyClient\Invalidation\ClearCapable;
17+
use FOS\HttpCache\ProxyClient\Invalidation\PrefixCapable;
1718
use FOS\HttpCache\ProxyClient\Invalidation\PurgeCapable;
1819
use FOS\HttpCache\ProxyClient\Invalidation\RefreshCapable;
1920
use FOS\HttpCache\ProxyClient\Invalidation\TagCapable;
@@ -103,6 +104,21 @@ public function testInvalidateTags(): void
103104
$this->assertSame($multiplexer, $multiplexer->invalidateTags($tags));
104105
}
105106

107+
public function testInvalidatePrefixes(): void
108+
{
109+
$prefixes = ['example.com/one/', 'example.com/two/'];
110+
111+
$mockClient = \Mockery::mock(PrefixCapable::class)
112+
->shouldReceive('invalidatePrefixes')
113+
->once()
114+
->with($prefixes)
115+
->getMock();
116+
117+
$multiplexer = new MultiplexerClient([$mockClient]);
118+
119+
$this->assertSame($multiplexer, $multiplexer->invalidatePrefixes($prefixes));
120+
}
121+
106122
public function testRefresh(): void
107123
{
108124
$url = 'example.com';

0 commit comments

Comments
 (0)