Skip to content

Commit acb3d7e

Browse files
committed
feat(http_cache): improve xkey purger implementation
1 parent e21a01a commit acb3d7e

File tree

22 files changed

+206
-147
lines changed

22 files changed

+206
-147
lines changed

composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@
4242
"friends-of-behat/mink-browserkit-driver": "^1.3.1",
4343
"friends-of-behat/mink-extension": "^2.2",
4444
"friends-of-behat/symfony-extension": "^2.1",
45-
"guzzlehttp/guzzle": "^6.0 || ^7.0",
4645
"jangregor/phpstan-prophecy": "^1.0",
4746
"justinrainbow/json-schema": "^5.2.1",
4847
"phpdocumentor/reflection-docblock": "^3.0 || ^4.0 || ^5.1",
@@ -97,7 +96,6 @@
9796
"suggest": {
9897
"doctrine/mongodb-odm-bundle": "To support MongoDB. Only versions 4.0 and later are supported.",
9998
"elasticsearch/elasticsearch": "To support Elasticsearch.",
100-
"guzzlehttp/guzzle": "To use the HTTP cache invalidation system.",
10199
"ocramius/package-versions": "To display the API Platform's version in the debug bar.",
102100
"phpdocumentor/reflection-docblock": "To support extracting metadata from PHPDoc.",
103101
"psr/cache-implementation": "To use metadata caching.",
@@ -106,6 +104,7 @@
106104
"symfony/cache": "To have metadata caching when using Symfony integration.",
107105
"symfony/config": "To load XML configuration files.",
108106
"symfony/expression-language": "To use authorization features.",
107+
"symfony/http-client": "To use the HTTP cache invalidation system.",
109108
"symfony/security": "To use authorization features.",
110109
"symfony/twig-bundle": "To use the Swagger UI integration.",
111110
"symfony/uid": "To support Symfony UUID/ULID identifiers.",

features/http_cache/tags.feature

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,12 @@ Feature: Cache invalidation through HTTP Cache tags
1818
"""
1919
Then the response status code should be 201
2020
And the header "Cache-Tags" should not exist
21-
And the header "xkey" should not exist
2221
And "/relation_embedders,/related_dummies,/third_levels" IRIs should be purged
23-
And "/relation_embedders /related_dummies /third_levels" IRIs should be purged with xkey
2422

2523
Scenario: Tags must be set for items
2624
When I send a "GET" request to "/relation_embedders/1"
2725
Then the response status code should be 200
2826
And the header "Cache-Tags" should be equal to "/relation_embedders/1,/related_dummies/1,/third_levels/1"
29-
And the header "xkey" should be equal to "/relation_embedders/1 /related_dummies/1 /third_levels/1"
3027

3128
Scenario: Create some more resources
3229
When I add "Content-Type" header equal to "application/ld+json"
@@ -41,13 +38,11 @@ Feature: Cache invalidation through HTTP Cache tags
4138
"""
4239
Then the response status code should be 201
4340
And the header "Cache-Tags" should not exist
44-
And the header "xkey" should not exist
4541

4642
Scenario: Tags must be set for collections
4743
When I send a "GET" request to "/relation_embedders"
4844
Then the response status code should be 200
4945
And the header "Cache-Tags" should be equal to "/relation_embedders/1,/related_dummies/1,/third_levels/1,/relation_embedders/2,/related_dummies/2,/third_levels/2,/relation_embedders"
50-
And the header "xkey" should be equal to "/relation_embedders/1 /related_dummies/1 /third_levels/1 /relation_embedders/2 /related_dummies/2 /third_levels/2 /relation_embedders"
5146

5247
Scenario: Purge item on update
5348
When I add "Content-Type" header equal to "application/ld+json"
@@ -59,18 +54,14 @@ Feature: Cache invalidation through HTTP Cache tags
5954
"""
6055
Then the response status code should be 200
6156
And the header "Cache-Tags" should not exist
62-
And the header "xkey" should not exist
6357
And "/relation_embedders,/relation_embedders/1,/related_dummies/1" IRIs should be purged
64-
And "/relation_embedders /relation_embedders/1 /related_dummies/1" IRIs should be purged with xkey
6558

6659
Scenario: Purge item and the related collection on update
6760
When I add "Content-Type" header equal to "application/ld+json"
6861
And I send a "DELETE" request to "/relation_embedders/1"
6962
Then the response status code should be 204
7063
And the header "Cache-Tags" should not exist
71-
And the header "xkey" should not exist
7264
And "/relation_embedders,/relation_embedders/1,/related_dummies/1" IRIs should be purged
73-
And "/relation_embedders /relation_embedders/1 /related_dummies/1" IRIs should be purged with xkey
7465

7566
Scenario: Create two Relation2
7667
When I add "Content-Type" header equal to "application/ld+json"
@@ -90,7 +81,6 @@ Feature: Cache invalidation through HTTP Cache tags
9081
Scenario: Embedded collection must be listed in cache tags
9182
When I send a "GET" request to "/relation2s/1"
9283
Then the header "Cache-Tags" should be equal to "/relation2s/1"
93-
Then the header "xkey" should be equal to "/relation2s/1"
9484

9585
Scenario: Create a Relation1
9686
When I add "Content-Type" header equal to "application/ld+json"
@@ -102,7 +92,6 @@ Feature: Cache invalidation through HTTP Cache tags
10292
"""
10393
Then the response status code should be 201
10494
And "/relation1s,/relation2s/1" IRIs should be purged
105-
And "/relation1s /relation2s/1" IRIs should be purged with xkey
10695

10796
Scenario: Update a Relation1
10897
When I add "Content-Type" header equal to "application/ld+json"
@@ -114,7 +103,6 @@ Feature: Cache invalidation through HTTP Cache tags
114103
"""
115104
Then the response status code should be 200
116105
And "/relation1s,/relation1s/1,/relation2s/2,/relation2s/1" IRIs should be purged
117-
And "/relation1s /relation1s/1 /relation2s/2 /relation2s/1" IRIs should be purged with xkey
118106

119107
Scenario: Create a Relation3 with many to many
120108
When I add "Content-Type" header equal to "application/ld+json"
@@ -126,14 +114,12 @@ Feature: Cache invalidation through HTTP Cache tags
126114
"""
127115
Then the response status code should be 201
128116
And "/relation3s,/relation2s/1,/relation2s/2" IRIs should be purged
129-
And "/relation3s /relation2s/1 /relation2s/2" IRIs should be purged with xkey
130117

131118
Scenario: Get a Relation3
132119
When I add "Content-Type" header equal to "application/ld+json"
133120
And I send a "GET" request to "/relation3s"
134121
Then the response status code should be 200
135122
And the header "Cache-Tags" should be equal to "/relation3s/1,/relation2s/1,/relation2s/2,/relation3s"
136-
And the header "xkey" should be equal to "/relation3s/1 /relation2s/1 /relation2s/2 /relation3s"
137123

138124
Scenario: Update a collection member only
139125
When I add "Content-Type" header equal to "application/ld+json"
@@ -145,16 +131,12 @@ Feature: Cache invalidation through HTTP Cache tags
145131
"""
146132
Then the response status code should be 200
147133
And the header "Cache-Tags" should not exist
148-
And the header "xkey" should not exist
149134
And "/relation3s,/relation3s/1,/relation2s/2,/relation2s,/relation2s/1" IRIs should be purged
150-
And "/relation3s /relation3s/1 /relation2s/2 /relation2s /relation2s/1" IRIs should be purged with xkey
151135

152136
Scenario: Delete the collection owner
153137
When I add "Content-Type" header equal to "application/ld+json"
154138
And I send a "DELETE" request to "/relation3s/1"
155139
Then the response status code should be 204
156140
And the header "Cache-Tags" should not exist
157-
And the header "xkey" should not exist
158141
And "/relation3s,/relation3s/1,/relation2s/2" IRIs should be purged
159-
And "/relation3s /relation3s/1 /relation2s/2" IRIs should be purged with xkey
160142

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
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+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Core\HttpCache;
15+
16+
/**
17+
* Purges resources from the cache.
18+
*
19+
* @author Kévin Dunglas <[email protected]>
20+
*
21+
* @experimental
22+
*/
23+
interface PurgerInterface
24+
{
25+
/**
26+
* Purges all responses containing the given resources from the cache.
27+
*
28+
* @param string[] $iris
29+
*/
30+
public function purge(array $iris);
31+
}

src/Doctrine/EventListener/PurgeHttpCacheListener.php

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,8 @@ final class PurgeHttpCacheListener
4242
private $resourceClassResolver;
4343
private $propertyAccessor;
4444
private $tags = [];
45-
private $xKeyPurger;
46-
private $xkeyEnabled;
47-
private $httpTagsEnabled;
4845

49-
public function __construct(PurgerInterface $purger, $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, PurgerInterface $xKeyPurger = null, bool $xkeyEnabled = false, bool $httpTagsEnabled = true)
46+
public function __construct(PurgerInterface $purger, $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null)
5047
{
5148
$this->purger = $purger;
5249
$this->iriConverter = $iriConverter;
@@ -56,9 +53,6 @@ public function __construct(PurgerInterface $purger, $iriConverter, ResourceClas
5653

5754
$this->resourceClassResolver = $resourceClassResolver;
5855
$this->propertyAccessor = $propertyAccessor ?? PropertyAccess::createPropertyAccessor();
59-
$this->xKeyPurger = $xKeyPurger;
60-
$this->xkeyEnabled = $xkeyEnabled;
61-
$this->httpTagsEnabled = $httpTagsEnabled;
6256
}
6357

6458
/**
@@ -115,13 +109,7 @@ public function postFlush(): void
115109
return;
116110
}
117111

118-
if ($this->httpTagsEnabled) {
119-
$this->purger->purge(array_values($this->tags));
120-
}
121-
122-
if ($this->xkeyEnabled && $this->xKeyPurger) {
123-
$this->xKeyPurger->purge(array_values($this->tags));
124-
}
112+
$this->purger->purge(array_values($this->tags));
125113

126114
$this->tags = [];
127115
}

src/HttpCache/EventListener/AddTagsListener.php

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
use ApiPlatform\Api\IriConverterInterface;
1717
use ApiPlatform\Api\UrlGeneratorInterface;
18+
use ApiPlatform\Core\HttpCache\PurgerInterface as LegacyPurgerInterface;
19+
use ApiPlatform\HttpCache\PurgerInterface;
1820
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
1921
use ApiPlatform\State\UriVariablesResolverTrait;
2022
use ApiPlatform\Util\OperationRequestInitiatorTrait;
@@ -41,17 +43,19 @@ final class AddTagsListener
4143
use UriVariablesResolverTrait;
4244

4345
private $iriConverter;
44-
private $xkeyEnabled;
45-
private $xkeyGlue;
46-
private $httpTagsEnabled;
46+
private $purger;
4747

48-
public function __construct($iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, bool $xkeyEnabled = false, string $xkeyGlue = ' ', bool $httpTagsEnabled = true)
48+
/**
49+
* @var LegacyPurgerInterface|PurgerInterface
50+
*
51+
* @param mixed $iriConverter
52+
* @param mixed|null $purger
53+
*/
54+
public function __construct($iriConverter, ResourceMetadataCollectionFactoryInterface $resourceMetadataCollectionFactory = null, $purger = null)
4955
{
5056
$this->iriConverter = $iriConverter;
5157
$this->resourceMetadataCollectionFactory = $resourceMetadataCollectionFactory;
52-
$this->xkeyEnabled = $xkeyEnabled;
53-
$this->xkeyGlue = $xkeyGlue;
54-
$this->httpTagsEnabled = $httpTagsEnabled;
58+
$this->purger = $purger;
5559
}
5660

5761
/**
@@ -83,12 +87,17 @@ public function onKernelResponse(ResponseEvent $event): void
8387
return;
8488
}
8589

86-
if ($this->httpTagsEnabled) {
90+
if ($this->purger instanceof LegacyPurgerInterface || !$this->purger) {
8791
$response->headers->set('Cache-Tags', implode(',', $resources));
92+
trigger_deprecation('api-platform/core', '2.7', sprintf('The interface "%s" is deprecated, use "%s" instead.', LegacyPurgerInterface::class, PurgerInterface::class));
93+
94+
return;
8895
}
8996

90-
if ($this->xkeyEnabled) {
91-
$response->headers->set('xkey', implode($this->xkeyGlue, $resources));
97+
$headers = $this->purger->getResponseHeaders($resources);
98+
99+
foreach ($headers as $key => $value) {
100+
$response->headers->set($key, $value);
92101
}
93102
}
94103
}

src/HttpCache/PurgerInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,11 @@ interface PurgerInterface
2828
* @param string[] $iris
2929
*/
3030
public function purge(array $iris);
31+
32+
/**
33+
* Get the response header containing purged tags.
34+
*
35+
* @param string[] $iris
36+
*/
37+
public function getResponseHeaders(array $iris): array;
3138
}

src/HttpCache/VarnishPurger.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace ApiPlatform\HttpCache;
1515

16-
use GuzzleHttp\ClientInterface;
16+
use Symfony\Contracts\HttpClient\HttpClientInterface;
1717

1818
/**
1919
* Purges Varnish.
@@ -31,7 +31,7 @@ final class VarnishPurger implements PurgerInterface
3131
private $maxHeaderLength;
3232

3333
/**
34-
* @param ClientInterface[] $clients
34+
* @param HttpClientInterface[] $clients
3535
*/
3636
public function __construct(array $clients, int $maxHeaderLength = self::DEFAULT_VARNISH_MAX_HEADER_LENGTH)
3737
{
@@ -83,6 +83,14 @@ public function purge(array $iris)
8383
}
8484
}
8585

86+
/**
87+
* {@inheritdoc}
88+
*/
89+
public function getResponseHeaders(array $iris): array
90+
{
91+
return ['Cache-Tags' => implode(',', $iris)];
92+
}
93+
8694
private function purgeRequest(array $iris)
8795
{
8896
// Create the regex to purge all tags in just one request

src/HttpCache/VarnishXKeyPurger.php

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
namespace ApiPlatform\HttpCache;
1515

16-
use GuzzleHttp\ClientInterface;
16+
use Symfony\Contracts\HttpClient\HttpClientInterface;
1717

1818
/**
1919
* Purges Varnish XKey.
@@ -31,7 +31,7 @@ final class VarnishXKeyPurger implements PurgerInterface
3131
private $xkeyGlue;
3232

3333
/**
34-
* @param ClientInterface[] $clients
34+
* @param HttpClientInterface[] $clients
3535
*/
3636
public function __construct(array $clients, int $maxHeaderLength = self::VARNISH_MAX_HEADER_LENGTH, string $xkeyGlue = ' ')
3737
{
@@ -56,6 +56,14 @@ public function purge(array $iris)
5656
}
5757
}
5858

59+
/**
60+
* {@inheritdoc}
61+
*/
62+
public function getResponseHeaders(array $iris): array
63+
{
64+
return ['xkey' => implode($this->xkeyGlue, $iris)];
65+
}
66+
5967
private function purgeIris(array $iris): void
6068
{
6169
foreach ($this->chunkKeys($iris) as $keys) {

0 commit comments

Comments
 (0)