Bicycle/System/Classes/Session.php
Egor Isaev b65603b4d0 dev
2026-06-23 10:44:18 +03:00

215 lines
5.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* @package Bicycle
* @author Egor Isaev
* @description Session.php
* @copyright (c) 04/06/2026
*/
namespace System\Classes;
/**
* Синглтон сессии с поддержкой именованных сессий.
*/
class Session
{
/** @var array<string,static> Пул экземпляров по имени сессии */
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;
}
}