1
0
Fork 0
mirror of https://github.com/archtechx/tenancy.git synced 2025-12-12 15:34:03 +00:00

[4.x] Only revert initialized bootstrappers (#1385)

* Only revert initialized bootstrappers (Tenancy::initializedBootstrappers)

* Fix use of @property across the codebase
This commit is contained in:
Samuel Štancl 2025-08-05 11:12:25 +02:00 committed by GitHub
parent f308e2f84d
commit 8f8af34c32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 110 additions and 4 deletions

View file

@ -46,7 +46,7 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
* tenant instances passed to $closeInMemoryConnectionUsing closures, * tenant instances passed to $closeInMemoryConnectionUsing closures,
* if you're setting that property as well. * if you're setting that property as well.
* *
* @property Closure(PDO, string)|null * @var Closure(PDO, string)|null
*/ */
public static Closure|null $persistInMemoryConnectionUsing = null; public static Closure|null $persistInMemoryConnectionUsing = null;
@ -59,7 +59,7 @@ class SQLiteDatabaseManager implements TenantDatabaseManager
* NOTE: The parameter provided to the closure is the Tenant * NOTE: The parameter provided to the closure is the Tenant
* instance, not a PDO connection. * instance, not a PDO connection.
* *
* @property Closure(Tenant)|null * @var Closure(Tenant)|null
*/ */
public static Closure|null $closeInMemoryConnectionUsing = null; public static Closure|null $closeInMemoryConnectionUsing = null;

View file

@ -20,6 +20,10 @@ class BootstrapTenancy
$tenant = $event->tenancy->tenant; $tenant = $event->tenancy->tenant;
$bootstrapper->bootstrap($tenant); $bootstrapper->bootstrap($tenant);
if (! in_array($bootstrapper::class, $event->tenancy->initializedBootstrappers)) {
$event->tenancy->initializedBootstrappers[] = $bootstrapper::class;
}
} }
event(new TenancyBootstrapped($event->tenancy)); event(new TenancyBootstrapped($event->tenancy));

View file

@ -15,8 +15,10 @@ class RevertToCentralContext
event(new RevertingToCentralContext($event->tenancy)); event(new RevertingToCentralContext($event->tenancy));
foreach (array_reverse($event->tenancy->getBootstrappers()) as $bootstrapper) { foreach (array_reverse($event->tenancy->getBootstrappers()) as $bootstrapper) {
if (in_array($bootstrapper::class, $event->tenancy->initializedBootstrappers)) {
$bootstrapper->revert(); $bootstrapper->revert();
} }
}
event(new RevertedToCentralContext($event->tenancy)); event(new RevertedToCentralContext($event->tenancy));
} }

View file

@ -35,6 +35,20 @@ class Tenancy
*/ */
public static array $findWith = []; public static array $findWith = [];
/**
* A list of bootstrappers that have been initialized.
*
* This is used when reverting tenancy, mainly if an exception
* occurs during bootstrapping, to ensure we don't revert
* bootstrappers that haven't been properly initialized
* (bootstrapped for the first time) previously.
*
* @internal
*
* @var list<class-string<TenancyBootstrapper>>
*/
public array $initializedBootstrappers = [];
/** Initialize tenancy for the passed tenant. */ /** Initialize tenancy for the passed tenant. */
public function initialize(Tenant|int|string $tenant): void public function initialize(Tenant|int|string $tenant): void
{ {
@ -192,7 +206,6 @@ class Tenancy
/** /**
* Run a callback for multiple tenants. * Run a callback for multiple tenants.
* More performant than running $tenant->run() one by one.
* *
* @param array<Tenant>|array<string|int>|\Traversable|string|int|null $tenants * @param array<Tenant>|array<string|int>|\Traversable|string|int|null $tenants
*/ */

View file

@ -0,0 +1,87 @@
<?php
use Illuminate\Support\Facades\Event;
use Stancl\Tenancy\Contracts\TenancyBootstrapper;
use Stancl\Tenancy\Contracts\Tenant;
use Stancl\Tenancy\Events\TenancyEnded;
use Stancl\Tenancy\Events\TenancyInitialized;
use Stancl\Tenancy\Listeners\BootstrapTenancy;
use Stancl\Tenancy\Listeners\RevertToCentralContext;
use Stancl\Tenancy\Tests\Etc\Tenant as TenantModel;
test('only bootstrappers that have been initialized are reverted', function () {
config(['tenancy.bootstrappers' => [
Initialized_DummyBootstrapperFoo::class,
Initialized_DummyBootstrapperBar::class,
Initialized_DummyBootstrapperBaz::class,
]]);
Event::listen(TenancyInitialized::class, BootstrapTenancy::class);
Event::listen(TenancyEnded::class, RevertToCentralContext::class);
// Only needs to be done in tests
app()->singleton(Initialized_DummyBootstrapperFoo::class);
app()->singleton(Initialized_DummyBootstrapperBar::class);
app()->singleton(Initialized_DummyBootstrapperBaz::class);
$tenant = TenantModel::create();
try {
$tenant->run(fn() => null);
// Should throw an exception
expect(true)->toBe(false);
} catch (Exception $e) {
// NOT 'baz fail in revert' as was the behavior before
// the commit that added this test
expect($e->getMessage())->toBe('bar fail in bootstrap');
}
expect(tenancy()->initializedBootstrappers)->toBe([
Initialized_DummyBootstrapperFoo::class,
]);
});
class Initialized_DummyBootstrapperFoo implements TenancyBootstrapper
{
public string $bootstrapped = 'uninitialized';
public function bootstrap(Tenant $tenant): void
{
$this->bootstrapped = 'bootstrapped';
}
public function revert(): void
{
$this->bootstrapped = 'reverted';
}
}
class Initialized_DummyBootstrapperBar implements TenancyBootstrapper
{
public string $bootstrapped = 'uninitialized';
public function bootstrap(Tenant $tenant): void
{
throw new Exception('bar fail in bootstrap');
}
public function revert(): void
{
$this->bootstrapped = 'reverted';
}
}
class Initialized_DummyBootstrapperBaz implements TenancyBootstrapper
{
public string $bootstrapped = 'uninitialized';
public function bootstrap(Tenant $tenant): void
{
$this->bootstrapped = 'bootstrapped';
}
public function revert(): void
{
throw new Exception('baz fail in revert');
}
}