From 38bb7dae55f18a69e382e9fd4eba3c8d9f18cd77 Mon Sep 17 00:00:00 2001 From: Vladimir Bryzgalov Date: Tue, 12 May 2026 18:01:43 +0500 Subject: [PATCH] feat(wpmc S2): make Plugin::boot() idempotent Add private \$booted flag; guard skips all re-registration on subsequent calls. TDD: RED (3x boot -> expectAdded->once() fails) then GREEN. Drop deprecated setAccessible() call (no-op since PHP 8.1). Co-Authored-By: Claude Sonnet 4.6 --- includes/class-plugin.php | 6 ++++++ tests/PluginBootTest.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/includes/class-plugin.php b/includes/class-plugin.php index 06a8087..b6381c8 100644 --- a/includes/class-plugin.php +++ b/includes/class-plugin.php @@ -6,6 +6,7 @@ namespace WPMultiCity; final class Plugin { private static ?Plugin $instance = null; + private bool $booted = false; public static function instance(): Plugin { @@ -36,6 +37,11 @@ final class Plugin public function boot(): void { + if ($this->booted) { + return; + } + $this->booted = true; + if (!$this->dependencies_satisfied()) { add_action('admin_notices', [$this, 'render_dependency_notice']); return; diff --git a/tests/PluginBootTest.php b/tests/PluginBootTest.php index 1c94174..1af03c5 100644 --- a/tests/PluginBootTest.php +++ b/tests/PluginBootTest.php @@ -29,4 +29,38 @@ final class PluginBootTest extends TestCase 'boot() must register FieldGroup on acf/init' ); } + + public function test_boot_is_idempotent_when_called_multiple_times(): void + { + if (!class_exists('ACF', false)) { + eval('class ACF {}'); + } + + // Reset the singleton's boot flag so this test is independent of + // whatever previous tests did to the static instance. (We will add + // the flag in GREEN.) + $plugin = Plugin::instance(); + $reflection = new \ReflectionClass($plugin); + if ($reflection->hasProperty('booted')) { + $prop = $reflection->getProperty('booted'); + $prop->setValue($plugin, false); + } + + \Brain\Monkey\Actions\expectAdded('init') + ->with([\WPMultiCity\Taxonomy::class, 'do_register']) + ->once(); + \Brain\Monkey\Actions\expectAdded('init') + ->with([\WPMultiCity\PostType::class, 'do_register']) + ->once(); + \Brain\Monkey\Actions\expectAdded('acf/init') + ->with([\WPMultiCity\FieldGroup::class, 'do_register']) + ->once(); + + $plugin->boot(); + $plugin->boot(); + $plugin->boot(); + + // Mockery verifies ->once() in tearDown. + self::assertTrue(true); + } }