Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,4 @@ Most configuration (to Craft and the extension itself) is handled directly by Cl

The `StaticCache::EVENT_BEFORE_PURGE` event fires immediately before each tag purge, including the collected end-of-request batch. Listeners can modify its `tags` or cancel the purge.

When a saved element purge proceeds, its non-null site URL is included in tag-based gateway API requests as the optional `fetchUrls` field. URLs are deduplicated, and the gateway asynchronously fetches them after a successful purge to repopulate the cache. Drafts, revisions, deletions, and canceled purges do not send URLs.
Web requests dispatch tag purges to the gateway with a Craft queue job. When a saved element purge proceeds, its non-null site URL is included as the optional `fetchUrls` field. URLs are deduplicated, and the gateway asynchronously fetches them after a successful purge to repopulate the cache. Drafts, revisions, deletions, and canceled purges do not enqueue URLs. Non-web purges execute the same job immediately.
1 change: 0 additions & 1 deletion src/HeaderEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
enum HeaderEnum: string
{
case CACHE_TAG = 'Cache-Tag';
case CACHE_PURGE_TAG = 'Cache-Purge-Tag';
case CACHE_PURGE_PREFIX = 'Cache-Purge-Prefix';
case CACHE_CONTROL = 'Cache-Control';
case CDN_CACHE_CONTROL = 'CDN-Cache-Control';
Expand Down
20 changes: 4 additions & 16 deletions src/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,36 +36,24 @@ public static function makeGatewayApiRequest(iterable $headers): ResponseInterfa
->values()
->all();

$tags = [];
$prefixes = [];

foreach ($headers as $name => $value) {
if (HeaderEnum::CACHE_PURGE_TAG->matches((string) $name)) {
$tags = $normalizeHeaderValue($value);

if (!empty($tags)) {
break;
}

continue;
}

if (HeaderEnum::CACHE_PURGE_PREFIX->matches((string) $name)) {
$prefixes = $normalizeHeaderValue($value);
break;
}
}

if (empty($tags) && empty($prefixes)) {
throw new Exception('Gateway API requests require a supported purge header.');
if (empty($prefixes)) {
throw new Exception('Gateway API requests require a Cache-Purge-Prefix header.');
}

return self::createGatewayApiClient()->request(
'POST',
'cache/purge',
[
RequestOptions::JSON => !empty($tags)
? ['tags' => $tags]
: ['prefixes' => $prefixes],
RequestOptions::JSON => ['prefixes' => $prefixes],
],
);
}
Expand Down
66 changes: 10 additions & 56 deletions src/StaticCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Craft;
use craft\base\ElementInterface;
use craft\cloud\events\PurgeEvent;
use craft\cloud\queue\PurgeStaticCacheJob;
use craft\events\ElementEvent;
use craft\events\InvalidateElementCachesEvent;
use craft\events\RegisterCacheOptionsEvent;
Expand All @@ -24,7 +25,7 @@
use yii\caching\TagDependency;

/**
* Static Cache tags can appear in the `Cache-Tag` and `Cache-Purge-Tag` headers.
* Static Cache tags can appear in the `Cache-Tag` header.
* The values are comma-separated and can be in several formats:
*
* - Added by the gateway:
Expand Down Expand Up @@ -348,14 +349,6 @@ private function sendPurgeTagsRequest(
Collection $tags,
?Collection $fetchUrls = null,
): void {
$response = Craft::$app->getResponse();
$isWebResponse = $response instanceof \craft\web\Response;

// Add any existing tags from the response headers
if ($isWebResponse) {
$tags->push(...$this->parseCacheTagsFromHeader(HeaderEnum::CACHE_PURGE_TAG->value));
}

$tags = $this->normalizeCacheTags(...$tags);

if ($tags->isEmpty()) {
Expand All @@ -367,10 +360,6 @@ private function sendPurgeTagsRequest(
]);
$this->trigger(self::EVENT_BEFORE_PURGE, $event);

if ($isWebResponse) {
$response->getHeaders()->remove(HeaderEnum::CACHE_PURGE_TAG->value);
}

$overflowTag = $this->overflowTag();
$tags = $event->isValid
? $this->normalizeCacheTags(...$event->tags)
Expand All @@ -383,53 +372,18 @@ private function sendPurgeTagsRequest(
}

$fetchUrls ??= Collection::make();
$sendApiRequest = !$isWebResponse || $fetchUrls->isNotEmpty();

if (!$sendApiRequest) {
$tags = $this->truncateCacheTagsForHeader($tags);

Module::info('Purging tags', [
'tags' => $tags,
]);

$this->setCacheTagHeader(
HeaderEnum::CACHE_PURGE_TAG->value,
$tags,
);

return;
}

Module::info('Purging tags', [
'tags' => $tags,
'fetchUrls' => $fetchUrls,
]);

$payload = [
$job = new PurgeStaticCacheJob([
'tags' => $tags->map(fn(StaticCacheTag $tag) => (string) $tag)->values()->all(),
];

if ($fetchUrls->isNotEmpty()) {
$payload['fetchUrls'] = $fetchUrls
'fetchUrls' => $fetchUrls
->unique()
->values()
->all();
}

try {
Helper::createGatewayApiClient()->request('POST', 'cache/purge', [
RequestOptions::JSON => $payload,
RequestOptions::TIMEOUT => 40,
]);
} catch (\Throwable $e) {
if ($isWebResponse) {
$this->setCacheTagHeader(
HeaderEnum::CACHE_PURGE_TAG->value,
$this->truncateCacheTagsForHeader($tags),
);
}
->all(),
]);

throw $e;
if (Craft::$app->getResponse() instanceof \craft\web\Response) {
Craft::$app->getQueue()->push($job);
} else {
$job->execute(Craft::$app->getQueue());
}
Comment on lines +383 to 387
}

Expand Down
47 changes: 47 additions & 0 deletions src/queue/PurgeStaticCacheJob.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace craft\cloud\queue;

use craft\cloud\Helper;
use craft\cloud\Module;
use craft\i18n\Translation;
use craft\queue\BaseJob;
use GuzzleHttp\RequestOptions;

class PurgeStaticCacheJob extends BaseJob
{
/**
* @var string[]
*/
public array $tags = [];

/**
* @var string[]
*/
public array $fetchUrls = [];

protected function defaultDescription(): ?string
{
return Translation::prep('app', 'Purging static cache');
}

public function execute($queue): void
{
Module::info('Purging tags', [
'tags' => $this->tags,
'fetchUrls' => $this->fetchUrls,
]);

$payload = [
'tags' => $this->tags,
];

if ($this->fetchUrls !== []) {
$payload['fetchUrls'] = $this->fetchUrls;
}

Helper::createGatewayApiClient()->request('POST', 'cache/purge', [
RequestOptions::JSON => $payload,
]);
Comment on lines +43 to +45
}
}
Loading
Loading