Пул экземпляров по имени сессии */ private static array $instances = []; /** @var bool Была ли сессия уничтожена */ protected bool $_destroyed = false; /** @var string Имя сессии */ protected string $_name = 'main'; /** @var int Время жизни cookie сессии в секундах */ protected int $_lifetime = 0; /** @var array Ссылка на данные сессии ($_SESSION) */ protected array $_data = []; /** * @param string $name Имя сессии * @param array $config Параметры cookie (lifetime, secure, httponly, samesite) */ private function __construct(string $name, array $config) { $this->_name = $name; $this->_lifetime = $config['lifetime'] ?? 0; session_write_close(); session_name($this->_name); session_set_cookie_params([ 'lifetime' => $this->_lifetime > 0 ? 300 : 0, 'path' => '/', 'secure' => $config['secure'] ?? false, 'httponly' => $config['httponly'] ?? true, 'samesite' => $config['samesite'] ?? 'Lax', ]); $this->read(); } /** * Возвращает (создавая при необходимости) именованный экземпляр сессии. * * @param string $name Имя сессии (регистронезависимо) * @return static */ public static function instance(string $name = 'main'): static { $name = strtolower($name); if (!isset(self::$instances[$name])) { self::$instances[$name] = new static($name, []); } return self::$instances[$name]; } /** * Запускает сессию, проверяет TTL и связывает $_data с $_SESSION. * * @return void */ public function read(): void { session_cache_limiter(false); session_start(); $_SESSION['start_time'] = (int)($_SESSION['start_time'] ?? time()); $gc_lifetime = (int)ini_get('session.gc_maxlifetime'); if ($_SESSION['start_time'] + $gc_lifetime < time()) { $this->destroy(); } $this->_data = &$_SESSION; } /** * @param string $key Ключ * @param mixed $default Значение по умолчанию * @return mixed */ public function get(string $key, mixed $default = null): mixed { return array_key_exists($key, $this->_data) ? $this->_data[$key] : $default; } /** * @param string $key Ключ * @param mixed $value Значение * @return static */ public function set(string $key, mixed $value): static { $this->_data[$key] = $value; return $this; } /** * @param string $key Ключ * @return static */ public function delete(string $key): static { unset($this->_data[$key]); return $this; } /** * Уничтожает сессию и связанные cookie. * * @return bool true, если сессия уничтожена */ public function destroy(): bool { if (!$this->_destroyed) { session_unset(); session_destroy(); setcookie(session_name(), '', 0, '/'); Cookie::delete($this->_name); if ($this->_destroyed = !session_id()) { $this->_data = []; } } return $this->_destroyed; } /** * Генерирует новый session_id, сохраняя данные. * * @return false|string Новый идентификатор или false */ public function regenerate(): false|string { session_regenerate_id(true); $new_id = session_id(); Cookie::set($this->_name, $new_id, $this->_lifetime); return $new_id; } /** * Записывает и закрывает сессию. * * @return bool false, если сессия уничтожена или заголовки уже отправлены */ public function write(): bool { if ($this->_destroyed || headers_sent()) { return false; } session_write_close(); return true; } /** * Привязывает ключ сессии к переменной по ссылке. * * @param string $key Ключ * @param mixed $value Переменная по ссылке * @return static */ public function bind(string $key, mixed &$value): static { $this->_data[$key] = &$value; return $this; } /** * @return false|string Текущий session_id */ public function id(): false|string { return session_id(); } /** * @return array Все данные сессии */ public function getData(): array { return $this->_data; } /** * Возвращает (создавая при необходимости) хэш устройства из cookie. * * @return string */ public static function getDeviceHash(): string { $hash = Cookie::get('device_hash'); if (!$hash) { $hash = md5(($_SERVER['HTTP_USER_AGENT'] ?? '') . uniqid('', true)); Cookie::set('device_hash', $hash, 365 * 24 * 3600); } return $hash; } }