From 2d7206fb163084920fa0850d08ec3ceb7ff57053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samuel=20=C5=A0tancl?= Date: Mon, 4 Aug 2025 01:40:44 +0200 Subject: [PATCH] Only revert initialized bootstrappers (Tenancy::initializedBootstrappers) --- src/Listeners/BootstrapTenancy.php | 4 ++ src/Listeners/RevertToCentralContext.php | 4 +- src/Tenancy.php | 13 +++- tests/InitializedBootstrappersTest.php | 87 ++++++++++++++++++++++++ 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 tests/InitializedBootstrappersTest.php diff --git a/src/Listeners/BootstrapTenancy.php b/src/Listeners/BootstrapTenancy.php index 50f38208..957949a4 100644 --- a/src/Listeners/BootstrapTenancy.php +++ b/src/Listeners/BootstrapTenancy.php @@ -20,6 +20,10 @@ class BootstrapTenancy $tenant = $event->tenancy->tenant; $bootstrapper->bootstrap($tenant); + + if (! in_array($bootstrapper::class, $event->tenancy->initializedBootstrappers)) { + $event->tenancy->initializedBootstrappers[] = $bootstrapper::class; + } } event(new TenancyBootstrapped($event->tenancy)); diff --git a/src/Listeners/RevertToCentralContext.php b/src/Listeners/RevertToCentralContext.php index 0a680532..312346ac 100644 --- a/src/Listeners/RevertToCentralContext.php +++ b/src/Listeners/RevertToCentralContext.php @@ -15,7 +15,9 @@ class RevertToCentralContext event(new RevertingToCentralContext($event->tenancy)); foreach (array_reverse($event->tenancy->getBootstrappers()) as $bootstrapper) { - $bootstrapper->revert(); + if (in_array($bootstrapper::class, $event->tenancy->initializedBootstrappers)) { + $bootstrapper->revert(); + } } event(new RevertedToCentralContext($event->tenancy)); diff --git a/src/Tenancy.php b/src/Tenancy.php index f96c0a51..12c8c41d 100644 --- a/src/Tenancy.php +++ b/src/Tenancy.php @@ -35,6 +35,18 @@ class Tenancy */ 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. + * + * @property list> + */ + public array $initializedBootstrappers = []; + /** Initialize tenancy for the passed tenant. */ public function initialize(Tenant|int|string $tenant): void { @@ -192,7 +204,6 @@ class Tenancy /** * Run a callback for multiple tenants. - * More performant than running $tenant->run() one by one. * * @param array|array|\Traversable|string|int|null $tenants */ diff --git a/tests/InitializedBootstrappersTest.php b/tests/InitializedBootstrappersTest.php new file mode 100644 index 00000000..549c527f --- /dev/null +++ b/tests/InitializedBootstrappersTest.php @@ -0,0 +1,87 @@ + [ + 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'); + } +}