From 81a5f573355d63654169f37b366ab8a9ceb0df9c Mon Sep 17 00:00:00 2001 From: Derrick Austin Date: Thu, 15 Oct 2026 16:39:26 -0500 Subject: [PATCH] Clear the static called class when a static extension method throws extendableCallStatic() set $extendableStaticCalledClass before forwarding the call and cleared it afterward. An exception from the extension skipped the clear, so the property kept the caller's class name for every later static call in the process. The clear now runs in a finally block. --- src/Extension/ExtendableTrait.php | 10 ++++--- tests/Extension/ExtendableTraitTest.php | 35 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/src/Extension/ExtendableTrait.php b/src/Extension/ExtendableTrait.php index d7d65a5d..08b2134f 100644 --- a/src/Extension/ExtendableTrait.php +++ b/src/Extension/ExtendableTrait.php @@ -508,9 +508,13 @@ public static function extendableCallStatic($name, $params = null) if (method_exists($extension, $name) && is_callable([$extension, $name])) { $extension::$extendableStaticCalledClass = $className; - $result = forward_static_call_array(array($extension, $name), $params); - $extension::$extendableStaticCalledClass = null; - return $result; + + try { + return forward_static_call_array(array($extension, $name), $params); + } + finally { + $extension::$extendableStaticCalledClass = null; + } } } diff --git a/tests/Extension/ExtendableTraitTest.php b/tests/Extension/ExtendableTraitTest.php index 91d21752..78640ffd 100644 --- a/tests/Extension/ExtendableTraitTest.php +++ b/tests/Extension/ExtendableTraitTest.php @@ -42,6 +42,41 @@ public function testGetExtensionParentClassOnExtendableTrait() $this->assertEquals(Level1NonExtendable::class, $this->callProtectedMethod($level2NonExtendable, 'extensionGetParentClass')->getName()); $this->assertEquals(Level1NonExtendable::class, $this->callProtectedMethod($level3NonExtendable, 'extensionGetParentClass')->getName()); } + + public function testStaticCalledClassIsClearedWhenAStaticExtensionMethodThrows() + { + try { + StaticThrowingHost::explode(); + $this->fail('The exception from the extension should propagate.'); + } + catch (\RuntimeException $ex) { + // Expected. + } + + $this->assertNull(StaticThrowingExtension::$extendableStaticCalledClass); + } +} + +class StaticThrowingHost +{ + use ExtendableTrait; + + public $implement = [StaticThrowingExtension::class]; + + public static function __callStatic($name, $params) + { + return self::extendableCallStatic($name, $params); + } +} + +class StaticThrowingExtension +{ + use \Winter\Storm\Extension\ExtensionTrait; + + public static function explode() + { + throw new \RuntimeException('static extension failed'); + } } class ExtendableRoot extends Extendable