1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2026-06-20 22:54:05 +00:00

Revert "Merge branch 'fix-cache-invalidation' into boilerplate-dev"

This reverts commit 32fa4cca20, reversing
changes made to 2dfbbef0f3.
This commit is contained in:
lukinovec 2026-06-02 12:53:53 +02:00
parent 32fa4cca20
commit ba4c80c615
9 changed files with 7 additions and 147 deletions

View file

@ -80,8 +80,8 @@ services:
mssql: mssql:
image: mcr.microsoft.com/mssql/server:2022-latest image: mcr.microsoft.com/mssql/server:2022-latest
environment: environment:
ACCEPT_EULA: "Y" - ACCEPT_EULA=Y
SA_PASSWORD: "P@ssword" # must be the same as TENANCY_TEST_SQLSRV_PASSWORD - SA_PASSWORD=P@ssword # must be the same as TENANCY_TEST_SQLSRV_PASSWORD
healthcheck: # https://github.com/Microsoft/mssql-docker/issues/133#issuecomment-1995615432 healthcheck: # https://github.com/Microsoft/mssql-docker/issues/133#issuecomment-1995615432
test: timeout 2 bash -c 'cat < /dev/null > /dev/tcp/127.0.0.1/1433' test: timeout 2 bash -c 'cat < /dev/null > /dev/tcp/127.0.0.1/1433'
interval: 10s interval: 10s

View file

@ -14,7 +14,7 @@ class PendingScope implements Scope
/** /**
* Apply the scope to a given Eloquent query builder. * Apply the scope to a given Eloquent query builder.
* *
* @param Builder<covariant Model> $builder * @param Builder<Model> $builder
* *
* @return void * @return void
*/ */

View file

@ -12,7 +12,7 @@ use Illuminate\Database\Eloquent\Scope;
class ParentModelScope implements Scope class ParentModelScope implements Scope
{ {
/** /**
* @param Builder<covariant Model> $builder * @param Builder<Model> $builder
*/ */
public function apply(Builder $builder, Model $model): void public function apply(Builder $builder, Model $model): void
{ {

View file

@ -13,7 +13,7 @@ use Stancl\Tenancy\Tenancy;
class TenantScope implements Scope class TenantScope implements Scope
{ {
/** /**
* @param Builder<covariant Model> $builder * @param Builder<Model> $builder
*/ */
public function apply(Builder $builder, Model $model) public function apply(Builder $builder, Model $model)
{ {

View file

@ -22,34 +22,12 @@ class DeleteDatabase implements ShouldQueue
protected TenantWithDatabase&Model $tenant, protected TenantWithDatabase&Model $tenant,
) {} ) {}
/** Skip database deletion if the create_database internal attribute is false. */
public static bool $skipWhenCreateDatabaseIsFalse = true;
/** Ignore exceptions thrown during database deletion and continue execution. */
public static bool $ignoreFailures = false;
public function handle(): void public function handle(): void
{ {
if (static::$skipWhenCreateDatabaseIsFalse && $this->tenant->getInternal('create_database') === false) {
// If database creation was skipped, we presume deletion should also be skipped.
// To avoid this skip, either unset the `create_database` attribute (or make it true), or
// set the $skipWhenCreateDatabaseIsFalse static property to false.
return;
}
event(new DeletingDatabase($this->tenant)); event(new DeletingDatabase($this->tenant));
$deleted = false; $this->tenant->database()->manager()->deleteDatabase($this->tenant);
try { event(new DatabaseDeleted($this->tenant));
$this->tenant->database()->manager()->deleteDatabase($this->tenant);
$deleted = true;
} catch (\Throwable $e) {
if (! static::$ignoreFailures) {
throw $e;
}
}
if ($deleted) event(new DatabaseDeleted($this->tenant));
} }
} }

View file

@ -6,11 +6,9 @@ namespace Stancl\Tenancy;
use Closure; use Closure;
use Illuminate\Cache\CacheManager; use Illuminate\Cache\CacheManager;
use Illuminate\Cache\DatabaseStore;
use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Console\Migrations\FreshCommand; use Illuminate\Database\Console\Migrations\FreshCommand;
use Illuminate\Routing\Events\RouteMatched; use Illuminate\Routing\Events\RouteMatched;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
@ -96,15 +94,6 @@ class TenancyServiceProvider extends ServiceProvider
// using the callback below. It is set by DatabaseCacheBootstrapper. // using the callback below. It is set by DatabaseCacheBootstrapper.
$manager = new CacheManager($app); $manager = new CacheManager($app);
// Make globalCache use either the configured non-null connection,
// or fall back to the central connection.
$this->makeDatabaseCacheStoresCentral($manager);
// If a bootstrapper (like DatabaseCacheBootstrapper) makes the
// cache connection tenant explicitly, the makeDatabaseCacheStoresCentral()
// call ends up setting the tenant connection rather than the central one,
// and the $adjustCacheManagerUsing callback is needed to
// make globalCache use the central connection.
if (static::$adjustCacheManagerUsing !== null) { if (static::$adjustCacheManagerUsing !== null) {
(static::$adjustCacheManagerUsing)($manager); (static::$adjustCacheManagerUsing)($manager);
} }
@ -113,34 +102,6 @@ class TenancyServiceProvider extends ServiceProvider
}); });
} }
/**
* Ensure globalCache uses the central connection for database cache stores.
*
* A freshly built CacheManager creates database stores using the current default connection, which
* DatabaseTenancyBootstrapper switches to the tenant connection. Since global cache should always be
* central, reset those stores back to their configured connection, falling back to the central one.
*/
protected function makeDatabaseCacheStoresCentral(CacheManager $manager): void
{
$centralConnection = $this->app['config']['tenancy.database.central_connection'];
foreach ($this->app['config']['cache.stores'] ?? [] as $name => $store) {
$notAValidDatabaseStore = ! is_array($store) || ($store['driver'] ?? null) !== 'database';
if ($notAValidDatabaseStore) {
continue;
}
/** @var DatabaseStore $databaseStore */
$databaseStore = $manager->store($name)->getStore();
// If $store['connection'] is null, it defaults to the default DB connection (which may be tenant).
// Fall back to the central connection to keep the global cache central.
$databaseStore->setConnection(DB::connection($store['connection'] ?? $centralConnection));
$databaseStore->setLockConnection(DB::connection($store['lock_connection'] ?? $store['connection'] ?? $centralConnection));
}
}
/* Bootstrap services. */ /* Bootstrap services. */
public function boot(): void public function boot(): void
{ {

View file

@ -165,7 +165,6 @@ test('cache is invalidated when tenant is updated from within the tenant context
['redis', [CacheTenancyBootstrapper::class]], ['redis', [CacheTenancyBootstrapper::class]],
['redis', [CacheTagsBootstrapper::class]], ['redis', [CacheTagsBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]], ['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, CacheTenancyBootstrapper::class]],
]); ]);
test('cache is invalidated when the tenant is deleted', function (string $resolver, bool $configureTenantModelColumn) { test('cache is invalidated when the tenant is deleted', function (string $resolver, bool $configureTenantModelColumn) {

View file

@ -2,27 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
use Stancl\JobPipeline\JobPipeline; use Stancl\JobPipeline\JobPipeline;
use Stancl\Tenancy\Events\TenantCreated; use Stancl\Tenancy\Events\TenantCreated;
use Stancl\Tenancy\Events\TenantDeleted;
use Stancl\Tenancy\Jobs\CreateDatabase; use Stancl\Tenancy\Jobs\CreateDatabase;
use Stancl\Tenancy\Jobs\DeleteDatabase;
use Stancl\Tenancy\Jobs\MigrateDatabase; use Stancl\Tenancy\Jobs\MigrateDatabase;
use Stancl\Tenancy\Jobs\SeedDatabase; use Stancl\Tenancy\Jobs\SeedDatabase;
use Stancl\Tenancy\Tests\Etc\Tenant; use Stancl\Tenancy\Tests\Etc\Tenant;
use Illuminate\Foundation\Auth\User as Authenticable; use Illuminate\Foundation\Auth\User as Authenticable;
use Stancl\Tenancy\Tests\Etc\TestSeeder; use Stancl\Tenancy\Tests\Etc\TestSeeder;
beforeEach($cleanup = function () {
DeleteDatabase::$ignoreFailures = false;
DeleteDatabase::$skipWhenCreateDatabaseIsFalse = true;
});
afterEach($cleanup);
test('database can be created after tenant creation', function () { test('database can be created after tenant creation', function () {
config(['tenancy.database.template_tenant_connection' => 'mysql']); config(['tenancy.database.template_tenant_connection' => 'mysql']);
@ -92,73 +82,6 @@ test('custom job can be added to the pipeline', function () {
}); });
}); });
test('database can be deleted after tenant deletion', function () {
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
Event::listen(TenantDeleted::class, JobPipeline::make([DeleteDatabase::class])->send(function (TenantDeleted $event) {
return $event->tenant;
})->toListener());
$tenant = Tenant::create();
$manager = $tenant->database()->manager();
expect($manager->databaseExists($tenant->database()->getName()))->toBeTrue();
$tenant->delete();
expect($manager->databaseExists($tenant->database()->getName()))->toBeFalse();
});
test('database deletion is skipped when create_database is false', function (bool $skipWhenCreateDatabaseIsFalse) {
Event::listen(TenantDeleted::class, JobPipeline::make([DeleteDatabase::class])->send(function (TenantDeleted $event) {
return $event->tenant;
})->toListener());
// create_database=false means no DB is created (e.g. tenant uses a pre-existing DB)
// On deletion, DeleteDatabase should skip rather than attempting DROP DATABASE on a non-existent DB
$tenant = Tenant::create(['tenancy_create_database' => false, 'tenancy_db_name' => 'non_existing_db']);
$manager = $tenant->database()->manager();
expect($manager->databaseExists($tenant->database()->getName()))->toBeFalse();
DeleteDatabase::$skipWhenCreateDatabaseIsFalse = $skipWhenCreateDatabaseIsFalse;
if ($skipWhenCreateDatabaseIsFalse) {
$tenant->delete(); // no exception
} else {
expect(fn () => $tenant->delete())->toThrow(QueryException::class, "database doesn't exist");
}
expect($manager->databaseExists($tenant->database()->getName()))->toBeFalse();
})->with([true, false]);
test('database deletion failure is ignored when ignoreFailures is true', function (bool $ignoreFailures) {
Event::listen(TenantCreated::class, JobPipeline::make([CreateDatabase::class])->send(function (TenantCreated $event) {
return $event->tenant;
})->toListener());
Event::listen(TenantDeleted::class, JobPipeline::make([DeleteDatabase::class])->send(function (TenantDeleted $event) {
return $event->tenant;
})->toListener());
DeleteDatabase::$ignoreFailures = $ignoreFailures;
$tenant = Tenant::create();
$manager = $tenant->database()->manager();
expect($manager->databaseExists($tenant->database()->getName()))->toBeTrue();
$manager->deleteDatabase($tenant); // manually delete so the job fails
expect($manager->databaseExists($tenant->database()->getName()))->toBeFalse();
if ($ignoreFailures) {
$tenant->delete(); // no exception
} else {
expect(fn () => $tenant->delete())->toThrow(QueryException::class, "database doesn't exist");
}
})->with([true, false]);
class User extends Authenticable class User extends Authenticable
{ {
protected $guarded = []; protected $guarded = [];

View file

@ -165,7 +165,6 @@ test('global cache is always central', function (string $store, array $bootstrap
['redis', [CacheTagsBootstrapper::class]], ['redis', [CacheTagsBootstrapper::class]],
['redis', [CacheTenancyBootstrapper::class]], ['redis', [CacheTenancyBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]], ['database', [DatabaseTenancyBootstrapper::class, DatabaseCacheBootstrapper::class]],
['database', [DatabaseTenancyBootstrapper::class, CacheTenancyBootstrapper::class]],
])->with([ ])->with([
'helper', 'helper',
'facade', 'facade',