diff --git a/src/Resolvers/Contracts/CachedTenantResolver.php b/src/Resolvers/Contracts/CachedTenantResolver.php index ddd5fde6..d78e1b48 100644 --- a/src/Resolvers/Contracts/CachedTenantResolver.php +++ b/src/Resolvers/Contracts/CachedTenantResolver.php @@ -48,6 +48,10 @@ abstract class CachedTenantResolver implements TenantResolver public function invalidateCache(Tenant $tenant): void { + if (! static::$shouldCache) { + return; + } + foreach ($this->getArgsForTenant($tenant) as $args) { $this->cache->forget($this->getCacheKey(...$args)); } diff --git a/src/Resolvers/DomainTenantResolver.php b/src/Resolvers/DomainTenantResolver.php index e54bc7e6..94d649d2 100644 --- a/src/Resolvers/DomainTenantResolver.php +++ b/src/Resolvers/DomainTenantResolver.php @@ -6,12 +6,20 @@ namespace Stancl\Tenancy\Resolvers; use Stancl\Tenancy\Contracts\Domain; use Stancl\Tenancy\Contracts\Tenant; -use Stancl\Tenancy\Contracts\TenantResolver; use Stancl\Tenancy\Exceptions\TenantCouldNotBeIdentifiedOnDomainException; -class DomainTenantResolver implements TenantResolver +class DomainTenantResolver extends Contracts\CachedTenantResolver { - public function resolve(...$args): Tenant + /** @var bool */ + public static $shouldCache = false; + + /** @var int */ + public static $cacheTTL = 3600; // seconds + + /** @var string|null */ + public static $cacheStore = null; // default + + public function resolveWithoutCache(...$args): Tenant { /** @var Domain $domain */ $domain = config('tenancy.domain_model')::where('domain', $args[0])->first(); @@ -22,4 +30,13 @@ class DomainTenantResolver implements TenantResolver throw new TenantCouldNotBeIdentifiedOnDomainException($domain); } + + public function getArgsForTenant(Tenant $tenant): array + { + $tenant->unsetRelation('domains'); + + return $tenant->domains->map(function (Domain $domain) { + return [$domain->domain]; + })->toArray(); + } } diff --git a/src/TenantDatabaseManagers/PostgreSQLSchemaManager.php b/src/TenantDatabaseManagers/PostgreSQLSchemaManager.php index cb5d17d0..9d815b25 100644 --- a/src/TenantDatabaseManagers/PostgreSQLSchemaManager.php +++ b/src/TenantDatabaseManagers/PostgreSQLSchemaManager.php @@ -36,7 +36,7 @@ class PostgreSQLSchemaManager implements TenantDatabaseManager public function deleteDatabase(TenantWithDatabase $tenant): bool { - return $this->database()->statement("DROP SCHEMA \"{$tenant->database()->getName()}\""); + return $this->database()->statement("DROP SCHEMA \"{$tenant->database()->getName()}\" CASCADE"); } public function databaseExists(string $name): bool diff --git a/tests/CachedTenantResolverTest.php b/tests/CachedTenantResolverTest.php index 98b8bf43..94962040 100644 --- a/tests/CachedTenantResolverTest.php +++ b/tests/CachedTenantResolverTest.php @@ -4,10 +4,7 @@ declare(strict_types=1); namespace Stancl\Tenancy\Tests; -use Illuminate\Support\Facades\Route; -use Stancl\Tenancy\Middleware\InitializeTenancyByDomain; -use Stancl\Tenancy\Middleware\InitializeTenancyByRequestData; -use Stancl\Tenancy\Resolvers\CachedTenantResolver; +use Illuminate\Support\Facades\DB; use Stancl\Tenancy\Resolvers\DomainTenantResolver; use Stancl\Tenancy\Tests\Etc\Tenant; @@ -15,8 +12,7 @@ class CachedTenantResolverTest extends TestCase { public function tearDown(): void { - InitializeTenancyByDomain::$shouldCache = false; - InitializeTenancyByRequestData::$shouldCache = false; + DomainTenantResolver::$shouldCache = false; parent::tearDown(); } @@ -30,7 +26,7 @@ class CachedTenantResolverTest extends TestCase ]); $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); - $this->assertTrue($tenant->is(app(CachedTenantResolver::class)->resolve(DomainTenantResolver::class, ['acme']))); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); } /** @test */ @@ -41,51 +37,78 @@ class CachedTenantResolverTest extends TestCase 'domain' => 'acme', ]); - $this->assertTrue($tenant->is(app(CachedTenantResolver::class)->resolve(DomainTenantResolver::class, ['acme']))); + DB::enableQueryLog(); - $this->mock(DomainTenantResolver::class, function ($mock) { - return $mock->shouldNotReceive('resolve'); - }); + DomainTenantResolver::$shouldCache = false; + + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + DB::flushQueryLog(); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + $this->assertNotEmpty(DB::getQueryLog()); // not empty - $this->assertTrue($tenant->is(app(CachedTenantResolver::class)->resolve(DomainTenantResolver::class, ['acme']))); + DomainTenantResolver::$shouldCache = true; + + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + DB::flushQueryLog(); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + $this->assertEmpty(DB::getQueryLog()); // empty } /** @test */ - public function caching_can_be_configured_selectively_on_initialization_middleware() + public function cache_is_invalidated_when_the_tenant_is_updated() { - InitializeTenancyByDomain::$shouldCache = true; - - $tenant = Tenant::create([ - 'id' => 'acme', - ]); - $tenant->domains()->create([ - 'domain' => 'acme.localhost', + $tenant = Tenant::create(); + $tenant->createDomain([ + 'domain' => 'acme', ]); - Route::get('/foo', function () { - return 'bar'; - })->middleware(InitializeTenancyByDomain::class); + DB::enableQueryLog(); - // create cache - $this->get('http://acme.localhost/foo') - ->assertSee('bar'); + DomainTenantResolver::$shouldCache = true; - $this->mock(CachedTenantResolver::class, function ($mock) { - return $mock->shouldReceive('resolve')->once(); // only once - }); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + DB::flushQueryLog(); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + $this->assertEmpty(DB::getQueryLog()); // empty - // use cache - $this->get('http://acme.localhost/foo') - ->assertSee('bar'); + $tenant->update([ + 'foo' => 'bar', + ]); - Route::get('/abc', function () { - return 'xyz'; - })->middleware(InitializeTenancyByRequestData::class); - - $this->get('/abc?tenant=acme') - ->assertSee('xyz'); - - $this->get('/abc?tenant=acme') - ->assertSee('xyz'); + DB::flushQueryLog(); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + $this->assertNotEmpty(DB::getQueryLog()); // not empty } + + /** @test */ + public function cache_is_invalidated_when_a_tenants_domain_is_changed() + { + $tenant = Tenant::create(); + $tenant->createDomain([ + 'domain' => 'acme', + ]); + + DB::enableQueryLog(); + + DomainTenantResolver::$shouldCache = true; + + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + DB::flushQueryLog(); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + $this->assertEmpty(DB::getQueryLog()); // empty + + $tenant->createDomain([ + 'domain' => 'bar', + ]); + + DB::flushQueryLog(); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('acme'))); + $this->assertNotEmpty(DB::getQueryLog()); // not empty + + DB::flushQueryLog(); + $this->assertTrue($tenant->is(app(DomainTenantResolver::class)->resolve('bar'))); + $this->assertNotEmpty(DB::getQueryLog()); // not empty + } + + // todo2 at some point in the future, we could write invalidation tests for the other resolvers too }