From 92e9dff6248b09db625d681c8ed3bb4c74c00578 Mon Sep 17 00:00:00 2001 From: klsoft-web Date: Mon, 28 Sep 2026 10:25:11 +0300 Subject: [PATCH] Fix reusing the previous session ID in Session::open() in long-running workers --- CHANGELOG.md | 1 + src/Session.php | 11 +++++++---- tests/MockSessionId.php | 17 +++++++++++++++++ tests/SessionTest.php | 13 +++++++++++++ 4 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 tests/MockSessionId.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af09c2..1a6c2c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Enh #86: Remove `yiisoft/cookies` dependency (@vjik) - Bug #86: `NullSession::getCookieParameters()` now returns proper cookie parameters instead of an empty array (@vjik) +- Bug #25: Fix reusing the previous session ID in `Session::open()` in long-running workers (@klsoft-web) ## 3.0.2 August 26, 2026 diff --git a/src/Session.php b/src/Session.php index c272959..e1c5564 100644 --- a/src/Session.php +++ b/src/Session.php @@ -5,6 +5,7 @@ namespace Yiisoft\Session; use SessionHandlerInterface; +use SessionHandler; use Throwable; use const PHP_SESSION_ACTIVE; @@ -37,14 +38,14 @@ final class Session implements SessionInterface /** * @param array $options Session options. See {@link https://www.php.net/manual/en/session.configuration.php}. - * @param SessionHandlerInterface|null $handler Session handler. If not specified, default PHP handler is used. + * @param SessionHandlerInterface|SessionHandler|null $handler Session handler. If not specified, default PHP handler is used. * * @psalm-param SessionOptions $options */ - public function __construct(array $options = [], ?SessionHandlerInterface $handler = null) + public function __construct(array $options = [], private SessionHandlerInterface|SessionHandler|null $handler = null) { - if ($handler !== null) { - session_set_save_handler($handler, true); + if ($this->handler !== null) { + session_set_save_handler($this->handler, true); } // We set cookies using SessionMiddleware. @@ -94,6 +95,8 @@ public function open(): void if ($this->sessionId !== null) { session_id($this->sessionId); + } else if ($this->handler instanceof SessionHandler) { + session_id($this->handler->create_sid()); } try { diff --git a/tests/MockSessionId.php b/tests/MockSessionId.php new file mode 100644 index 0000000..d674faa --- /dev/null +++ b/tests/MockSessionId.php @@ -0,0 +1,17 @@ +sessionId; + } +} diff --git a/tests/SessionTest.php b/tests/SessionTest.php index 5cb781a..775eade 100644 --- a/tests/SessionTest.php +++ b/tests/SessionTest.php @@ -94,6 +94,19 @@ public function testRegenerateIdOpensInactiveSession(): void self::assertNotEquals($id, $session->getId()); } + public function testOpenDoesNotReuseIdOfClosedSession(): void + { + $firstSession = new Session([], new MockSessionId('first-session-id')); + $firstSession->open(); + $firstId = $firstSession->getId(); + $firstSession->close(); + + $secondSession = new Session([], new MockSessionId('second-session-id')); + $secondSession->open(); + + self::assertNotSame($firstId, $secondSession->getId()); + } + public function testDiscard(): void { $session = $this->getSession();