From af00030dd9a88b915b1ea0fd1e14ea29165da420 Mon Sep 17 00:00:00 2001 From: Ralf Lang Date: Tue, 21 Jul 2026 14:25:28 +0200 Subject: [PATCH] fix: Regression on PHP 8.x for connection check --- lib/Horde/Imap/Client/Base.php | 11 ++- .../Base/BaseLogoutNullConnectionTest.php | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 test/Unit/Base/BaseLogoutNullConnectionTest.php diff --git a/lib/Horde/Imap/Client/Base.php b/lib/Horde/Imap/Client/Base.php index 7ef486de..98cd373a 100644 --- a/lib/Horde/Imap/Client/Base.php +++ b/lib/Horde/Imap/Client/Base.php @@ -748,8 +748,8 @@ public function getNamespaces( * "Array to string conversion" on PHP 8+. */ $normalized = []; foreach ($additional as $val) { - if (is_array($val) || - (is_object($val) && !($val instanceof Stringable))) { + if (is_array($val) + || (is_object($val) && !($val instanceof Stringable))) { continue; } $normalized[] = strval($val); @@ -902,7 +902,12 @@ abstract protected function _login(); */ public function logout() { - if ($this->_isAuthenticated && $this->_connection->connected) { + // shutdown() reaches this via register_shutdown_function; if the + // client was torn down before _connection was materialized we + // still want to zero the state below, so guard the connection + // read explicitly rather than relying on _isAuthenticated as a + // proxy - PHP 8+ warns on ->connected of null. + if ($this->_isAuthenticated && $this->_connection !== null && $this->_connection->connected) { $this->_logout(); $this->_connection->close(); } diff --git a/test/Unit/Base/BaseLogoutNullConnectionTest.php b/test/Unit/Base/BaseLogoutNullConnectionTest.php new file mode 100644 index 00000000..a7b1ab62 --- /dev/null +++ b/test/Unit/Base/BaseLogoutNullConnectionTest.php @@ -0,0 +1,70 @@ +_connection->connected` when `_isAuthenticated` was true. + * If an authenticated client was torn down before _connection was + * materialized (or after it had been cleared and _isAuthenticated + * still flagged true), PHP 8+ raised + * "Attempt to read property 'connected' on null" from shutdown(). + * PHPUnit's `failOnWarning="true"` in phpunit.xml.dist turns the + * warning into a test failure — the assertion is implicit. + */ +#[CoversNothing] +class BaseLogoutNullConnectionTest extends TestCase +{ + /** + * When _isAuthenticated is true but _connection is null, + * logout() must not warn on the ->connected read; it should + * zero the state and return cleanly. + */ + public function testLogoutWithAuthenticatedFlagAndNullConnectionDoesNotWarn(): void + { + $ob = new Base([ + 'username' => 'user', + 'password' => 'pass', + ]); + + // Force the exact state the CI stack trace observed: + // _isAuthenticated = true, _connection = null. Reflection + // is needed because both properties are protected and no + // setter exists (nor should one — this state is only + // reachable through internal lifecycle races). + $reflection = new ReflectionClass($ob); + + $authProperty = $reflection->getProperty('_isAuthenticated'); + $authProperty->setValue($ob, true); + + $connProperty = $reflection->getProperty('_connection'); + $connProperty->setValue($ob, null); + + // logout() is where the warning fired. If it warns here, the + // test fails via phpunit.xml.dist's failOnWarning="true". + $ob->logout(); + + // State should be zeroed regardless of the pre-call condition. + $this->assertFalse($authProperty->getValue($ob)); + $this->assertNull($connProperty->getValue($ob)); + } +}