diff --git a/CHANGELOG.md b/CHANGELOG.md index 6af09c2..113a854 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/README.md b/README.md index 04c37bc..66ab84d 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,17 @@ $session = new \Yiisoft\Session\Session([], $handler); Custom storage must implement `\SessionHandlerInterface`. +## Custom session ID + +When using `Yiisoft\Session\Session` as session component, you can provide your own implementation of the session ID creator: + +```php +$idCreator = new MySessionId(); +$session = new \Yiisoft\Session\Session([], null, $idCreator); +``` + +The custom session ID creator must implement the `\SessionIdInterface`. + ## Documentation - [Internals](docs/internals.md) diff --git a/config/di-web.php b/config/di-web.php index d85a590..2ff935c 100644 --- a/config/di-web.php +++ b/config/di-web.php @@ -15,6 +15,7 @@ '__construct()' => [ $params['yiisoft/session']['session']['options'], $params['yiisoft/session']['session']['handler'], + $params['yiisoft/session']['session']['idCreator'], ], 'reset' => function () { $this->sessionId = null; diff --git a/config/params.php b/config/params.php index ace9550..abe337c 100644 --- a/config/params.php +++ b/config/params.php @@ -7,6 +7,7 @@ 'session' => [ 'options' => ['cookie_secure' => 0], 'handler' => null, + 'idCreator' => null, ], ], ]; diff --git a/src/Session.php b/src/Session.php index c272959..1dc11df 100644 --- a/src/Session.php +++ b/src/Session.php @@ -5,6 +5,7 @@ namespace Yiisoft\Session; use SessionHandlerInterface; +use SessionIdInterface; use Throwable; use const PHP_SESSION_ACTIVE; @@ -38,10 +39,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 SessionIdInterface|null $idCreator Session id creator. * * @psalm-param SessionOptions $options */ - public function __construct(array $options = [], ?SessionHandlerInterface $handler = null) + public function __construct( + array $options = [], + ?SessionHandlerInterface $handler = null, + private ?SessionIdInterface $idCreator = null) { if ($handler !== null) { session_set_save_handler($handler, true); @@ -94,6 +99,8 @@ public function open(): void if ($this->sessionId !== null) { session_id($this->sessionId); + } else if ($this->idCreator !== null) { + session_id($this->idCreator->create_sid()); } try { diff --git a/tests/MockSessionId.php b/tests/MockSessionId.php new file mode 100644 index 0000000..c03231f --- /dev/null +++ b/tests/MockSessionId.php @@ -0,0 +1,17 @@ +sessionId; + } +} diff --git a/tests/SessionTest.php b/tests/SessionTest.php index 5cb781a..1ce66e3 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([], null, new MockSessionId('first-session-id')); + $firstSession->open(); + $firstId = $firstSession->getId(); + $firstSession->close(); + + $secondSession = new Session([], null, new MockSessionId('second-session-id')); + $secondSession->open(); + + self::assertNotSame($firstId, $secondSession->getId()); + } + public function testDiscard(): void { $session = $this->getSession();