d[$k] ?? null; } public function set(string $k, mixed $v): void { $this->d[$k] = $v; } public function clear(): void { $this->d = []; } /** * Rotates the id only — the DATA survives, matching PhpSessionStore's * session_regenerate_id(true). This double used to wipe the array, which diverged from the * real store and hid a live question: whether the csrf token seeded before login is still * there after it (it is — that is what /logout's CSRF check relies on). */ public function regenerate(): void { /* id is not modeled; data is preserved */ } } final class PhpSessionStore implements SessionStore { /** @param string $cookiePath Scope the cookie to the mount ('/' at the web root, * '/move_detect/' in a subdirectory) so a co-hosted site * sharing the same www/ is never sent this session id. */ public function __construct(string $cookiePath = '/') { if (session_status() !== PHP_SESSION_ACTIVE) session_start([ 'cookie_httponly' => true, 'cookie_secure' => true, 'cookie_samesite' => 'Lax', 'cookie_path' => $cookiePath, ]); } public function get(string $k): mixed { return $_SESSION[$k] ?? null; } public function set(string $k, mixed $v): void { $_SESSION[$k] = $v; } public function clear(): void { $_SESSION = []; session_destroy(); } public function regenerate(): void { session_regenerate_id(true); } }