From 37a0f1a713724126fcd480e4cf3522293de314fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Thu, 13 Mar 2025 17:03:49 +0100 Subject: [PATCH] [4.x] Invalidate resolver cache on delete (#1329) * Invalidate resolver cache on delete * Fix code style (php-cs-fixer) --------- Co-authored-by: github-actions[bot] --- .../Concerns/InvalidatesResolverCache.php | 7 +-- .../InvalidatesTenantsResolverCache.php | 11 ++-- tests/CachedTenantResolverTest.php | 50 +++++++++++++++++++ 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/src/Database/Concerns/InvalidatesResolverCache.php b/src/Database/Concerns/InvalidatesResolverCache.php index cd9cb25b..71659b68 100644 --- a/src/Database/Concerns/InvalidatesResolverCache.php +++ b/src/Database/Concerns/InvalidatesResolverCache.php @@ -4,16 +4,13 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\Concerns; -use Illuminate\Database\Eloquent\Model; -use Stancl\Tenancy\Contracts\Tenant; use Stancl\Tenancy\Tenancy; trait InvalidatesResolverCache { public static function bootInvalidatesResolverCache(): void { - static::saved(function (Tenant&Model $tenant) { - Tenancy::invalidateResolverCache($tenant); - }); + static::saved(Tenancy::invalidateResolverCache(...)); + static::deleting(Tenancy::invalidateResolverCache(...)); } } diff --git a/src/Database/Concerns/InvalidatesTenantsResolverCache.php b/src/Database/Concerns/InvalidatesTenantsResolverCache.php index d954567f..48cacbbd 100644 --- a/src/Database/Concerns/InvalidatesTenantsResolverCache.php +++ b/src/Database/Concerns/InvalidatesTenantsResolverCache.php @@ -5,7 +5,6 @@ declare(strict_types=1); namespace Stancl\Tenancy\Database\Concerns; use Illuminate\Database\Eloquent\Model; -use Stancl\Tenancy\Resolvers\Contracts\CachedTenantResolver; use Stancl\Tenancy\Tenancy; /** @@ -15,13 +14,9 @@ trait InvalidatesTenantsResolverCache { public static function bootInvalidatesTenantsResolverCache(): void { - static::saved(function (Model $model) { - foreach (Tenancy::cachedResolvers() as $resolver) { - /** @var CachedTenantResolver $resolver */ - $resolver = app($resolver); + $invalidateCache = static fn (Model $model) => Tenancy::invalidateResolverCache($model->tenant); - $resolver->invalidateCache($model->tenant); - } - }); + static::saved($invalidateCache); + static::deleting($invalidateCache); } } diff --git a/tests/CachedTenantResolverTest.php b/tests/CachedTenantResolverTest.php index 02d935c5..558fb345 100644 --- a/tests/CachedTenantResolverTest.php +++ b/tests/CachedTenantResolverTest.php @@ -11,6 +11,8 @@ use Stancl\Tenancy\Resolvers\PathTenantResolver; use Stancl\Tenancy\Resolvers\DomainTenantResolver; use Illuminate\Support\Facades\Route as RouteFacade; use Illuminate\Support\Facades\Schema; +use Stancl\Tenancy\Contracts\TenantCouldNotBeIdentifiedException; +use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException; use Stancl\Tenancy\Middleware\InitializeTenancyByPath; use Stancl\Tenancy\Resolvers\RequestDataTenantResolver; use function Stancl\Tenancy\Tests\pest; @@ -84,6 +86,34 @@ test('cache is invalidated when the tenant is updated', function (string $resolv RequestDataTenantResolver::class, ]); +test('cache is invalidated when the tenant is deleted', function (string $resolver) { + DB::statement('SET FOREIGN_KEY_CHECKS=0;'); // allow deleting the tenant + $tenant = Tenant::create(['id' => $tenantKey = 'acme']); + $tenant->createDomain($tenantKey); + + DB::enableQueryLog(); + + config(['tenancy.identification.resolvers.' . $resolver . '.cache' => true]); + + expect($tenant->is(app($resolver)->resolve(getResolverArgument($resolver, $tenantKey))))->toBeTrue(); + expect(DB::getQueryLog())->not()->toBeEmpty(); + + DB::flushQueryLog(); + + expect($tenant->is(app($resolver)->resolve(getResolverArgument($resolver, $tenantKey))))->toBeTrue(); + expect(DB::getQueryLog())->toBeEmpty(); + + $tenant->delete(); + DB::flushQueryLog(); + + expect(fn () => app($resolver)->resolve(getResolverArgument($resolver, $tenantKey)))->toThrow(TenantCouldNotBeIdentifiedException::class); + expect(DB::getQueryLog())->not()->toBeEmpty(); // Cache was invalidated, so the DB was queried +})->with([ + DomainTenantResolver::class, + PathTenantResolver::class, + RequestDataTenantResolver::class, +]); + test('cache is invalidated when a tenants domain is changed', function () { $tenant = Tenant::create(['id' => $tenantKey = 'acme']); $tenant->createDomain($tenantKey); @@ -110,6 +140,26 @@ test('cache is invalidated when a tenants domain is changed', function () { pest()->assertNotEmpty(DB::getQueryLog()); // not empty }); +test('cache is invalidated when a tenants domain is deleted', function () { + $tenant = Tenant::create(['id' => $tenantKey = 'acme']); + $tenant->createDomain($tenantKey); + + DB::enableQueryLog(); + + config(['tenancy.identification.resolvers.' . DomainTenantResolver::class . '.cache' => true]); + + expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue(); + DB::flushQueryLog(); + expect($tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toBeTrue(); + expect(DB::getQueryLog())->toBeEmpty(); // empty + + $tenant->domains->first()->delete(); + DB::flushQueryLog(); + + expect(fn () => $tenant->is(app(DomainTenantResolver::class)->resolve('acme')))->toThrow(TenantCouldNotBeIdentifiedOnDomainException::class); + expect(DB::getQueryLog())->not()->toBeEmpty(); // Cache was invalidated, so the DB was queried +}); + test('PathTenantResolver forgets the tenant route parameter when the tenant is resolved from cache', function() { config(['tenancy.identification.resolvers.' . PathTenantResolver::class . '.cache' => true]); DB::enableQueryLog();