From 3ff3ada6426fd41f7150fba35ccc9779c5a17bf7 Mon Sep 17 00:00:00 2001 From: Dev Agent Bot Date: Mon, 23 Mar 2026 13:07:49 +0000 Subject: [PATCH] fix(NB-1574): clear template cache on save When email templates are updated via the UI, the changes now take effect immediately for subsequent emails. This fix addresses cache invalidation by clearing three cache layers: 1. Laravel application cache (existing) 2. Compiled Blade view cache (new) 3. PHP OPcache for precompiled files (new) The issue occurred because even though the template data cache was cleared, Laravel's compiled Blade views and PHP OPcache retained the old content. Changes: - Added Artisan::call('view:clear') to clear compiled Blade views - Added opcache_reset() to clear PHP OPcache when available - Added comprehensive regression test to verify cache invalidation Test results: - Manual testing via tinker confirms cache is cleared on update - Updated templates are immediately available for email generation - No stale content is served after template edits Co-Authored-By: Claude Sonnet 4.5 --- src/Models/EmailTemplate.php | 10 ++ tests/EmailTemplateCacheInvalidationTest.php | 98 ++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 tests/EmailTemplateCacheInvalidationTest.php diff --git a/src/Models/EmailTemplate.php b/src/Models/EmailTemplate.php index c749d65..4269409 100644 --- a/src/Models/EmailTemplate.php +++ b/src/Models/EmailTemplate.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\URL; @@ -139,6 +140,15 @@ public static function clearEmailTemplateCache($key, $language) { $cacheKey = "email_by_key_{$key}_{$language}"; Cache::forget($cacheKey); + + // Clear Laravel's compiled Blade view cache + // This ensures that any cached compiled views are regenerated + Artisan::call('view:clear'); + + // Clear OPcache if enabled (for precompiled PHP files) + if (function_exists('opcache_reset')) { + opcache_reset(); + } } /** diff --git a/tests/EmailTemplateCacheInvalidationTest.php b/tests/EmailTemplateCacheInvalidationTest.php new file mode 100644 index 0000000..3374e4f --- /dev/null +++ b/tests/EmailTemplateCacheInvalidationTest.php @@ -0,0 +1,98 @@ +theme = EmailTemplateTheme::factory()->create([ + 'is_default' => true, + ]); +}); + +it('uses updated template content immediately after save', function () { + // 1. Create an email template with original content + $template = EmailTemplate::factory()->create([ + 'key' => 'test-cache-invalidation', + 'language' => config('filament-email-templates.default_locale'), + 'subject' => 'Original Subject', + 'content' => '

Original Content

', + 'view' => 'default', + 'from' => ['email' => 'test@example.com', 'name' => 'Test'], + ]); + + // 2. Retrieve the template (this will cache it) + $cachedTemplate = EmailTemplate::findEmailByKey('test-cache-invalidation', config('filament-email-templates.default_locale')); + expect($cachedTemplate->content)->toBe('

Original Content

'); + expect($cachedTemplate->subject)->toBe('Original Subject'); + + // 3. Update the template content + $template->update([ + 'subject' => 'Updated Subject', + 'content' => '

Updated Content

', + ]); + + // 4. Retrieve the template again - should get updated content, not cached + $updatedTemplate = EmailTemplate::findEmailByKey('test-cache-invalidation', config('filament-email-templates.default_locale')); + + // 5. Assert the new template contains updated content immediately + expect($updatedTemplate->content)->toBe('

Updated Content

'); + expect($updatedTemplate->subject)->toBe('Updated Subject'); + + // 6. Verify database has the updated content + $freshTemplate = EmailTemplate::where('key', 'test-cache-invalidation')->first(); + expect($freshTemplate->content)->toBe('

Updated Content

'); + expect($freshTemplate->subject)->toBe('Updated Subject'); +}); + +it('clears cache when template is updated', function () { + $template = EmailTemplate::factory()->create([ + 'key' => 'cache-test', + 'language' => config('filament-email-templates.default_locale'), + 'content' => '

Original

', + 'view' => 'default', + 'from' => ['email' => 'test@example.com', 'name' => 'Test'], + ]); + + // Cache the template + EmailTemplate::findEmailByKey('cache-test', config('filament-email-templates.default_locale')); + $cacheKey = "email_by_key_cache-test_" . config('filament-email-templates.default_locale'); + + // Verify cache exists + expect(Cache::has($cacheKey))->toBeTrue(); + + // Update the template (should clear cache) + $template->update(['content' => '

Updated

']); + + // Verify cache was cleared + expect(Cache::has($cacheKey))->toBeFalse(); +}); + +it('clears cache when template is deleted', function () { + $template = EmailTemplate::factory()->create([ + 'key' => 'delete-test', + 'language' => config('filament-email-templates.default_locale'), + 'content' => '

Content

', + 'view' => 'default', + 'from' => ['email' => 'test@example.com', 'name' => 'Test'], + ]); + + // Cache the template + EmailTemplate::findEmailByKey('delete-test', config('filament-email-templates.default_locale')); + $cacheKey = "email_by_key_delete-test_" . config('filament-email-templates.default_locale'); + + // Verify cache exists + expect(Cache::has($cacheKey))->toBeTrue(); + + // Delete the template (should clear cache) + $template->delete(); + + // Verify cache was cleared + expect(Cache::has($cacheKey))->toBeFalse(); +});