215 lines
5.8 KiB
PHP
215 lines
5.8 KiB
PHP
<?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;
|
||
}
|
||
}
|