Skip to content
Merged
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
12 changes: 12 additions & 0 deletions README.md
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update the readme to explain how our own event tracking work in conjunction with the Statamic Static Cache events. It's either or, right? Let's make that clear. Add the same comments as in the config file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated README to match config comments and clearly explain that event handling is either addon legacy events or Statamic Static Cache events (not both simultaneously). Addressed in commit 5d80754.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Automatically purge your Cloudflare cache when content changes in Statamic.
- Automatically purges Cloudflare cache when Statamic content changes.
- **Multi-zone support** for Statamic multisite installations with different domains.
- Configurable events that trigger cache purging.
- Optional support for Statamic `static_caching` invalidation rules via `UrlInvalidated`/`StaticCacheCleared` events.
- Optional queuing of purge jobs for background processing.
- CLI command for manual cache purging.
- Simple configuration with backward compatibility.
Expand Down Expand Up @@ -79,6 +80,11 @@ Once configured, the addon will automatically purge the Cloudflare cache when co

You can configure which events trigger cache purging in the config file.

#### Event handling modes (either/or)

- **Default (`use_statamic_static_cache_invalidation = false`)**: uses this addon’s own content events (`entry_*`, `term_*`, `asset_*`, etc.).
- **Statamic static cache mode (`use_statamic_static_cache_invalidation = true`)**: uses Statamic Static Cache events (`url_invalidated`, `static_cache_cleared`) and skips the addon’s legacy content events.

#### Queued Purging

If you prefer to handle cache purging in the background to avoid potential delays during web requests, you can enable queued purging. Set the `CLOUDFLARE_CACHE_QUEUE_PURGE` environment variable to `true` or set `'queue_purge' => true` in the configuration file.
Expand Down Expand Up @@ -136,8 +142,14 @@ return [
'nav_tree_saved' => true,
'global_set_saved' => true,
'global_set_deleted' => true,
'url_invalidated' => true, // Statamic Static Cache: UrlInvalidated event
'static_cache_cleared' => true, // Statamic Static Cache: StaticCacheCleared event
],

// When enabled, only Statamic Static Cache events are handled (UrlInvalidated/StaticCacheCleared).
// Legacy addon content events are skipped in this mode.
'use_statamic_static_cache_invalidation' => env('CLOUDFLARE_CACHE_USE_STATAMIC_STATIC_CACHE_INVALIDATION', false),

// Dispatch purge jobs to the queue instead of running synchronously
'queue_purge' => env('CLOUDFLARE_CACHE_QUEUE_PURGE', false),

Expand Down
6 changes: 6 additions & 0 deletions config/cloudflare-cache.php
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be clear of how it works. Our own event handling + the new Statamic Static Cache events. We're not using both simultaneously?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, it is either/or. I clarified this in both config comments and README under an "Event handling modes (either/or)" section. Addressed in commit 5d80754.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should log if there is any mis-configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented debug logging for misconfiguration-related skips in the listener so misconfiguration is visible when cloudflare-cache.debug is enabled. Addressed in commit 5d80754.

Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,14 @@
'nav_tree_saved' => true,
'global_set_saved' => true,
'global_set_deleted' => true,
'url_invalidated' => true, // Statamic Static Cache: UrlInvalidated event
'static_cache_cleared' => true, // Statamic Static Cache: StaticCacheCleared event
],

// When enabled, only Statamic Static Cache events are handled (UrlInvalidated/StaticCacheCleared).
// Legacy addon content events are skipped in this mode.
'use_statamic_static_cache_invalidation' => env('CLOUDFLARE_CACHE_USE_STATAMIC_STATIC_CACHE_INVALIDATION', false),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does enabling this disable our own event handling? In that case it should be made clear with a comment of how it works.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes — enabling use_statamic_static_cache_invalidation switches to Statamic Static Cache events only. I added explicit inline config comments and README wording to make the either/or behavior clear. Addressed in commit 5d80754.


'queue_purge' => env('CLOUDFLARE_CACHE_QUEUE_PURGE', false), // Dispatch purge jobs to the queue

/*
Expand Down
8 changes: 8 additions & 0 deletions src/CloudflareCacheServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
use Statamic\Events\NavTreeSaved;
use Statamic\Events\GlobalSetSaved;
use Statamic\Events\GlobalSetDeleted;
use Statamic\Events\UrlInvalidated;
use Statamic\Events\StaticCacheCleared;

class CloudflareCacheServiceProvider extends AddonServiceProvider
{
Expand Down Expand Up @@ -53,6 +55,12 @@ class CloudflareCacheServiceProvider extends AddonServiceProvider
GlobalSetDeleted::class => [
PurgeCloudflareCache::class,
],
UrlInvalidated::class => [
PurgeCloudflareCache::class,
],
StaticCacheCleared::class => [
PurgeCloudflareCache::class,
],
];

/**
Expand Down
54 changes: 53 additions & 1 deletion src/Listeners/PurgeCloudflareCache.php
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add the debug logging to show if and why something is mis-configured.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added debug logs for skipped events and mode mismatches (including disabled addon, mode mismatch, missing mapping, and disabled purge_on keys) to help diagnose misconfiguration. Addressed in commit 5d80754.

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Statamic\Events\NavTreeSaved;
use Statamic\Events\GlobalSetSaved;
use Statamic\Events\GlobalSetDeleted;
use Statamic\Events\UrlInvalidated;
use Statamic\Events\StaticCacheCleared;
use Statamic\Facades\URL;
use Illuminate\Support\Facades\Log; // Already present, but good to confirm

Expand All @@ -31,6 +33,11 @@ public function __construct(Client $client)
public function handle(Event $event): void
{
if (!config('cloudflare-cache.enabled')) {
if (config('cloudflare-cache.debug')) {
Log::debug('[Cloudflare Cache] Skipping purge because addon is disabled.', [
'event' => get_class($event),
]);
}
return;
}

Expand Down Expand Up @@ -108,7 +115,28 @@ protected function purgeSynchronously(array $urls): void
*/
protected function shouldHandleEvent(Event $event): bool
{
$useStatamicInvalidation = config('cloudflare-cache.use_statamic_static_cache_invalidation', false);
$isStaticCacheInvalidationEvent = $event instanceof UrlInvalidated || $event instanceof StaticCacheCleared;
$eventClass = get_class($event);

if ($useStatamicInvalidation && !$isStaticCacheInvalidationEvent) {
if (config('cloudflare-cache.debug')) {
Log::debug('[Cloudflare Cache] Skipping event because Statamic static cache invalidation mode is enabled and this is a legacy addon event.', [
'event' => $eventClass,
]);
}
return false;
}

if (!$useStatamicInvalidation && $isStaticCacheInvalidationEvent) {
if (config('cloudflare-cache.debug')) {
Log::debug('[Cloudflare Cache] Skipping event because Statamic static cache invalidation mode is disabled.', [
'event' => $eventClass,
]);
}
return false;
}

$eventMap = [
'Statamic\Events\EntrySaved' => 'entry_saved',
'Statamic\Events\EntryDeleted' => 'entry_deleted',
Expand All @@ -120,11 +148,31 @@ protected function shouldHandleEvent(Event $event): bool
'Statamic\Events\NavTreeSaved' => 'nav_tree_saved',
'Statamic\Events\GlobalSetSaved' => 'global_set_saved',
'Statamic\Events\GlobalSetDeleted' => 'global_set_deleted',
'Statamic\Events\UrlInvalidated' => 'url_invalidated',
'Statamic\Events\StaticCacheCleared' => 'static_cache_cleared',
];

$configKey = $eventMap[$eventClass] ?? null;

return $configKey && config("cloudflare-cache.purge_on.{$configKey}");
if (!$configKey) {
if (config('cloudflare-cache.debug')) {
Log::debug('[Cloudflare Cache] Skipping event because no purge_on mapping exists.', [
'event' => $eventClass,
]);
}
return false;
}

$shouldHandle = (bool) config("cloudflare-cache.purge_on.{$configKey}");

if (!$shouldHandle && config('cloudflare-cache.debug')) {
Log::debug('[Cloudflare Cache] Skipping event because purge_on setting is disabled.', [
'event' => $eventClass,
'config_key' => "cloudflare-cache.purge_on.{$configKey}",
]);
}

return $shouldHandle;
}

/**
Expand Down Expand Up @@ -186,6 +234,10 @@ protected function getUrlsToPurge(Event $event): array
// existing purge_everything_fallback logic (sync or queued).
}

if ($event instanceof UrlInvalidated && $event->url) {
$urls[] = URL::makeAbsolute($event->url);
}

$urls = array_filter($urls);

return array_unique($urls);
Expand Down
143 changes: 143 additions & 0 deletions tests/Feature/PurgeCacheListenerTest.php
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure we have test covering any mis-configurations as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added focused listener tests for misconfiguration paths, including logging when events are skipped due to mode mismatch. Addressed in commit 5d80754.

Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@
use Statamic\Events\EntrySaved;
use Statamic\Events\GlobalSetSaved;
use Statamic\Events\GlobalSetDeleted;
use Statamic\Events\UrlInvalidated;
use Statamic\Events\StaticCacheCleared;
use Statamic\Contracts\Entries\Entry;
use Statamic\Contracts\Entries\Collection;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Queue; // Add Queue facade
use Illuminate\Support\Facades\Log;
use Eminos\StatamicCloudflareCache\Events\CachePurged;
use Eminos\StatamicCloudflareCache\Jobs\PurgeCloudflareCacheJob;
use Mockery;
Expand Down Expand Up @@ -355,4 +358,144 @@ public function it_fires_cache_purged_event_after_synchronous_purge_everything()
return $e->purgedEverything && empty($e->urls);
});
}

#[Test]
public function it_ignores_legacy_events_when_statamic_static_cache_invalidation_is_enabled()
{
Event::fake([CachePurged::class]);
config([
'cloudflare-cache.use_statamic_static_cache_invalidation' => true,
'cloudflare-cache.queue_purge' => false,
]);

$entry = $this->mockEntry();
$event = new EntrySaved($entry);

$clientMock = $this->mock(Client::class);
$clientMock->shouldNotReceive('purgeUrls');
$clientMock->shouldNotReceive('purgeEverything');

$listener = $this->app->make(PurgeCloudflareCache::class);
$listener->handle($event);

Queue::assertNothingPushed();
Event::assertNotDispatched(CachePurged::class);
}

#[Test]
public function it_purges_invalidated_urls_when_statamic_static_cache_invalidation_is_enabled()
{
Event::fake([CachePurged::class]);
config([
'cloudflare-cache.use_statamic_static_cache_invalidation' => true,
'cloudflare-cache.queue_purge' => false,
]);

$event = new UrlInvalidated('/test-entry', 'https://example.com');

$clientMock = $this->mock(Client::class);
$clientMock->shouldReceive('purgeUrls')->once()->with(['https://example.com/test-entry']);
$clientMock->shouldNotReceive('purgeEverything');

$listener = $this->app->make(PurgeCloudflareCache::class);
$listener->handle($event);

Queue::assertNothingPushed();
Event::assertDispatched(CachePurged::class, function ($e) {
return !$e->purgedEverything && $e->urls === ['https://example.com/test-entry'];
});
}

#[Test]
public function it_purges_everything_when_static_cache_is_cleared_and_statamic_static_cache_invalidation_is_enabled()
{
config([
'cloudflare-cache.use_statamic_static_cache_invalidation' => true,
'cloudflare-cache.queue_purge' => false,
'cloudflare-cache.purge_everything_fallback' => true,
]);

$event = new StaticCacheCleared();

$clientMock = $this->mock(Client::class);
$clientMock->shouldReceive('purgeEverything')->once();
$clientMock->shouldNotReceive('purgeUrls');

$listener = $this->app->make(PurgeCloudflareCache::class);
$listener->handle($event);

Queue::assertNothingPushed();
}

#[Test]
public function it_does_not_purge_when_static_cache_is_cleared_and_fallback_is_disabled()
{
config([
'cloudflare-cache.use_statamic_static_cache_invalidation' => true,
'cloudflare-cache.queue_purge' => false,
'cloudflare-cache.purge_everything_fallback' => false,
]);

$event = new StaticCacheCleared();

$clientMock = $this->mock(Client::class);
$clientMock->shouldNotReceive('purgeEverything');
$clientMock->shouldNotReceive('purgeUrls');

$listener = $this->app->make(PurgeCloudflareCache::class);
$listener->handle($event);

Queue::assertNothingPushed();
}

#[Test]
public function it_logs_when_legacy_event_is_skipped_in_statamic_static_cache_invalidation_mode()
{
config([
'cloudflare-cache.use_statamic_static_cache_invalidation' => true,
'cloudflare-cache.debug' => true,
]);

Log::shouldReceive('debug')->once()->withArgs(function ($message, $context = []) {
return str_contains($message, 'Statamic static cache invalidation mode is enabled')
&& ($context['event'] ?? null) === EntrySaved::class;
});

$entry = $this->mockEntry();
$event = new EntrySaved($entry);

$clientMock = $this->mock(Client::class);
$clientMock->shouldNotReceive('purgeUrls');
$clientMock->shouldNotReceive('purgeEverything');

$listener = $this->app->make(PurgeCloudflareCache::class);
$listener->handle($event);

Queue::assertNothingPushed();
}

#[Test]
public function it_logs_when_static_cache_event_is_skipped_while_mode_is_disabled()
{
config([
'cloudflare-cache.use_statamic_static_cache_invalidation' => false,
'cloudflare-cache.debug' => true,
]);

Log::shouldReceive('debug')->once()->withArgs(function ($message, $context = []) {
return str_contains($message, 'Statamic static cache invalidation mode is disabled')
&& ($context['event'] ?? null) === UrlInvalidated::class;
});

$event = new UrlInvalidated('/test-entry', 'https://example.com');

$clientMock = $this->mock(Client::class);
$clientMock->shouldNotReceive('purgeUrls');
$clientMock->shouldNotReceive('purgeEverything');

$listener = $this->app->make(PurgeCloudflareCache::class);
$listener->handle($event);

Queue::assertNothingPushed();
}
}