diff --git a/src/Contracts/SessionInterface.php b/src/Contracts/SessionInterface.php index 2985fda..3f7d50c 100644 --- a/src/Contracts/SessionInterface.php +++ b/src/Contracts/SessionInterface.php @@ -79,4 +79,11 @@ public function dequeueMessages(): array; * Check if there are any messages in the queue. */ public function hasQueuedMessages(): bool; + + /** + * Get the session handler instance. + * + * @return SessionHandlerInterface + */ + public function getHandler(): SessionHandlerInterface; } diff --git a/src/Session/Session.php b/src/Session/Session.php index d7f1285..cc8f2e9 100644 --- a/src/Session/Session.php +++ b/src/Session/Session.php @@ -25,22 +25,49 @@ class Session implements SessionInterface public function __construct( protected SessionHandlerInterface $handler, - protected string $id = '' + protected string $id = '', + ?array $data = null ) { if (empty($this->id)) { $this->id = $this->generateId(); } - if ($data = $this->handler->read($this->id)) { - $this->data = json_decode($data, true) ?? []; + if ($data !== null) { + $this->hydrate($data); + } elseif ($sessionData = $this->handler->read($this->id)) { + $this->data = json_decode($sessionData, true) ?? []; } } + /** + * Create a session instance from handler or return null if session doesn't exist + */ + public static function make(string $id, SessionHandlerInterface $handler): ?SessionInterface + { + $sessionData = $handler->read($id); + + if (!$sessionData) { + return null; + } + + $data = json_decode($sessionData, true); + if ($data === null) { + return null; + } + + return new static($handler, $id, $data); + } + public function getId(): string { return $this->id; } + public function getHandler(): SessionHandlerInterface + { + return $this->handler; + } + public function generateId(): string { return bin2hex(random_bytes(16)); diff --git a/src/Session/SessionManager.php b/src/Session/SessionManager.php index 0edd63c..c0aa8b8 100644 --- a/src/Session/SessionManager.php +++ b/src/Session/SessionManager.php @@ -38,19 +38,24 @@ public function startGcTimer(): void return; } - $this->gcTimer = $this->loop->addPeriodicTimer($this->gcInterval, function () { - $deletedSessions = $this->handler->gc($this->ttl); - - foreach ($deletedSessions as $sessionId) { - $this->emit('session_deleted', [$sessionId]); - } - - if (count($deletedSessions) > 0) { - $this->logger->debug('Session garbage collection complete', [ - 'purged_sessions' => count($deletedSessions), - ]); - } - }); + $this->gcTimer = $this->loop->addPeriodicTimer($this->gcInterval, [$this, 'gc']); + } + + public function gc(): array + { + $deletedSessions = $this->handler->gc($this->ttl); + + foreach ($deletedSessions as $sessionId) { + $this->emit('session_deleted', [$sessionId]); + } + + if (count($deletedSessions) > 0) { + $this->logger->debug('Session garbage collection complete', [ + 'purged_sessions' => count($deletedSessions), + ]); + } + + return $deletedSessions; } /** @@ -93,13 +98,7 @@ public function createSession(string $sessionId): SessionInterface */ public function getSession(string $sessionId): ?SessionInterface { - $session = new Session($this->handler, $sessionId); - - if (empty($session->all())) { - return null; - } - - return $session; + return Session::make($sessionId, $this->handler); } /** diff --git a/tests/Unit/Session/SessionManagerTest.php b/tests/Unit/Session/SessionManagerTest.php index 29f6257..ff28ebf 100644 --- a/tests/Unit/Session/SessionManagerTest.php +++ b/tests/Unit/Session/SessionManagerTest.php @@ -89,7 +89,7 @@ }); it('returns null from getSession if session data is empty after load', function () { - $this->sessionHandler->shouldReceive('read')->with(SESSION_ID_MGR_1)->once()->andReturn(json_encode([])); + $this->sessionHandler->shouldReceive('read')->with(SESSION_ID_MGR_1)->once()->andReturn(false); $session = $this->sessionManager->getSession(SESSION_ID_MGR_1); expect($session)->toBeNull(); }); @@ -131,8 +131,12 @@ $sessionData = ['message_queue' => []]; $this->sessionHandler->shouldReceive('read')->with(SESSION_ID_MGR_1)->andReturn(json_encode($sessionData)); $message = '{"id":1}'; - $updatedSessionData = ['message_queue' => [$message]]; - $this->sessionHandler->shouldReceive('write')->with(SESSION_ID_MGR_1, json_encode($updatedSessionData))->once()->andReturn(true); + + $this->sessionHandler->shouldReceive('write')->with(SESSION_ID_MGR_1, Mockery::on(function ($dataJson) use ($message) { + $data = json_decode($dataJson, true); + expect($data['message_queue'])->toEqual([$message]); + return true; + }))->once()->andReturn(true); $this->sessionManager->queueMessage(SESSION_ID_MGR_1, $message); }); @@ -147,7 +151,11 @@ $messages = ['{"id":1}', '{"id":2}']; $sessionData = ['message_queue' => $messages]; $this->sessionHandler->shouldReceive('read')->with(SESSION_ID_MGR_1)->andReturn(json_encode($sessionData)); - $this->sessionHandler->shouldReceive('write')->with(SESSION_ID_MGR_1, json_encode(['message_queue' => []]))->once()->andReturn(true); + $this->sessionHandler->shouldReceive('write')->with(SESSION_ID_MGR_1, Mockery::on(function ($dataJson) { + $data = json_decode($dataJson, true); + expect($data['message_queue'])->toEqual([]); + return true; + }))->once()->andReturn(true); $dequeued = $this->sessionManager->dequeueMessages(SESSION_ID_MGR_1); expect($dequeued)->toEqual($messages); @@ -187,7 +195,7 @@ $sessionHandler = new ArraySessionHandler(60, $clock); $sessionHandler->write('sess_expired', 'data'); - $clock->addSeconds(100); + // $clock->addSeconds(100); $manager = new SessionManager( $sessionHandler,