From 77d46cf02ca162fcc37eb174c0476e7ca9090bf9 Mon Sep 17 00:00:00 2001 From: albertlast Date: Wed, 29 Jul 2026 18:22:45 +0200 Subject: [PATCH] Gives each action subclass its own instance in ActionTrait::load() ActionTrait declares $obj as a static property, and a static property is shared with every descendant class that does not redeclare it. None of the eleven action classes that extend another action redeclare it, so they all share one slot with their parent. Once the parent has been loaded, load() finds that slot occupied and returns the parent's instance, which does not satisfy the "static" return type: SMF\Actions\Login2::load(): Return value must be of type SMF\Actions\Logout, SMF\Actions\Login2 returned This is reachable during login: User::enforceBans() calls Logout::call() to kick a banned member, by which point Login2 has already been loaded, so a banned member gets a fatal error instead of being logged out. Checks that the cached instance is of the class being loaded, rather than merely present. Co-Authored-By: Claude Opus 5 --- Sources/ActionTrait.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/ActionTrait.php b/Sources/ActionTrait.php index cacd323b991..5cadd786c9e 100644 --- a/Sources/ActionTrait.php +++ b/Sources/ActionTrait.php @@ -115,7 +115,10 @@ public function canShowDebuggingInfo(): bool */ public static function load(): static { - if (!isset(static::$obj)) { + // A static property is shared with every descendant class that doesn't + // redeclare it, so $obj might currently hold an instance of a relative + // of this class rather than an instance of this class itself. + if (!isset(static::$obj) || static::$obj::class !== static::class) { static::$obj = new static(); }