Хранение PHP-сессий в базе данных
Подход, позволяющий хранить PHP-сессии в базе данных вместо файлов
Начну сразу с причин, по которым я пишу эту статью. Я периодически просматриваю лог запросов, по которому люди попадают сюда, и вот один из запросов — хранить php сессию в mysql
.
Итак, как же хранить PHP-сессии в базе данных?
На самом деле, в этом нет ничего сложного: в PHP есть одна полезная функция — session_set_save_handler — которая и выполняет всю грязную работу:
session_set_save_handler() sets the user-level session storage functions which are used for storing and retrieving data associated with a session. This is most useful when a storage method other than those supplied by PHP sessions is preferred. i.e. Storing the session data in a local database.
Эта функция принимает шесть аргументов типа сallback:
- open — вызывается при открытии/создании сессии;
- close — вызывается при закрытии сессии (например, чтобы приложение могло закрыть файл);
- read — чтение сессии;
- write — запись сессии;
- destroy — уничтожение сессии (
session_destroy()); - gc — «сборка мусора»
Все функции (кроме read) должны возвращать true, если все прошло успешно и false в противном случае.
Для хранения сессии у нас есть такая таблица:
`id` CHARACTER(32) BINARY NOT NULL PRIMARY KEY, /* Session ID */
`expires` INTEGER NOT NULL, /* Время истекания сессии */
`session_data` TEXT NOT NULL, /* Данные, хранящиеся в сессии */
KEY(`expires`)
)
Один маленький нюанс: если в MySQL делать таблицу сессий типа MEMORY (т.е. задать ей такой storage engine), то могут возникнуть проблемы с удалением записей при сборке мусора, ибо MEMORY storage engine не может использовать индексы для операций сравнения типа «больше»/«меньше».
Для абстракции от конкретного SQL-сервера, будем считать, что у нас имеется некий класс Persistent, обладающий способностями загружать/сохранять данные в базу данных.
Таким образом, реализация класса Session будет иметь следующий вид:
require_once('class.Persistent.php');
class Session extends Persistent
{
/**
* @var string
*/
public $id;
/**
* @var int
*/
public $expires;
/**
* @var string
*/
public $session_data;
/**
* @var bool
*/
public $m_new;
function __contruct($data)
{
parent::__construct($data);
}
static function getTable()
{
return TABLE_SESSION; //константа, задающая имя таблицы сессий
}
}
?>
Пока все предельно просто. О переменной $m_new поговорим позже.
Теперь собственно реализация класса, управляющего сессиями:
require_once('class.Session.php');
class SessionManager
{
/**
* @var int
*/
protected $life_time;
/**
* @var Session
*/
protected $session;
/**
* @return SessionManager
*/
public static function& instance($reinit = false)
{
static $self = null;
if (true == is_null($self)) {
$self = new SessionManager();
$reinit = true;
}
if (true == $reinit) {
session_set_save_handler(
array(&$self, "open"),
array(&$self, "close"),
array(&$self, "read"),
array(&$self, "write"),
array(&$self, "destroy"),
array(&$self, "gc")
);
register_shutdown_function('session_write_close');
}
return $self;
}
public function open($save_path, $sess_name)
{
$this->life_time = intval(get_cfg_var('session.gc_maxlifetime'));
$this->session = new Session(
array(
'id' => (true == isset($_COOKIE[$sess_name])) ? $_COOKIE[$sess_name] : session_id(),
'expires' => time() + $this->life_time,
'session_data' => '',
)
);
$this->session->m_new = true;
return true;
}
public function close()
{
return true;
}
public function read($sid)
{
$this->session = Session::load('Session', $sid, 3600);
if (false == $this->session instanceof Session) {
$this->session = new Session(
array(
'id' => (true == isset($_COOKIE[session_name()])) ? $_COOKIE[session_name()] : session_id(),
'expires' => time() + $this->life_time,
'session_data' => '',
)
);
$this->session->m_new = true;
}
else {
$this->session->m_new = false;
}
return (string)$this->session->session_data; //Явное приведение типа позволит избежать трудноуловимых ошибок
}
public function write($sid, $data)
{
$this->session->m_new |= ($sid != $this->session->id);
$this->session->id = $sid;
$this->session->session_data = $data;
$this->session->expires = time() + $this->life_time;
$mode = (true == $this->session->m_new) ? SAVE_INSERT : SAVE_UPDATE;
$this->session->save($mode);
return true;
}
public function destroy($sid)
{
unset($_COOKIE[$sid]);
Session::deleteMany('Session', new QueryCondition(array('id' => $sid)));
return true;
}
public function gc($max_time)
{
Session::deleteMany('Session', new QueryCondition("`expires` < '" . time() . "'"));
return true;
}
/**
* @return Session
*/
public function& getSession()
{
return $this->session;
}
}
?>
Пример использования:
$sm = &SessionManager::instance();
session_start();
?>
Вкратце о $m_new. Дело в том, что ID сессии при ее открытии может не совпадать с ID при закрытии (иными словами, измениться в ходе выполнения скрипта). Один из способов — это использование функции session_regenerate_id(). Поэтому при сохранении сессии важно знать, является ID новым или нет (если ID новый, то для сохранения будет использоваться INSERT, если существующий — то UPDATE; по большому счету можно обойтись одним REPLACE, однако это не особо эффективное решение). Для этого при открытии сессии получаем текущий идентификатор сессии и сравниваем его с тем, который получаем при сохранении сессии (конечно, в этом простом случае можно было обойтись без лишней переменной, но в более сложных проектах она может понадобиться); в зависимости от их равенства/неравенства используем тот или иной метод сохранения данных.
Строка register_shutdown_function('session_write_close') гарантирует, что сессия будет сохранена (без нее в PHP4 у меня были случаи, когда сессия не сохранялась).
О том, как прочитать данные из Session::session_data, можно прочитать в этой статье. Сразу отмечу, что PHP самостоятельно выполняет восстановление данных, которые ему передаются функцией SessionManager::read(), просто иногда бывают ситуации, когда сессию нужно восстановить вручную (подпатчить на лету).
При более глубоком изучении вопроса также будет полезна эта замечательная статья.
Связанные записи
Автор: Vladimir; опубликовано в: MySQL, PHP; метки: MySQL, PHP, база данных, сессияМарт
2008
Комментарии к статье «Хранение PHP-сессий в базе данных» (19) »
Оставить комментарий к записи «Хранение PHP-сессий в базе данных»
Вы должны быть авторизованы, чтобы иметь возможность оставить комментарий.

Меня зовут Владимир, я программист-фрилансер, специализирующийся на Web-программировании и програмировании под Linux.
По совместительству занимаюсь администрированием LAMP/LNMP-серверов и техническим переводом.






Vladimir не могли бы вы оставить свою аську, или почту чтобы связаться?
Или стукнуть мне?
Контактная информация здесь.
[...] Продолжение статьи «Хранение PHP-сессий в базе данных». [...]
Хранение PHP-сессий в кэше xCache
в PHP5 session_write_close следует вызывать из деструктора. В PHP4 – деструкторов еще не было, конечно
Все понял, очень понравилось. Но в упор не понимаю, что должны делать методы load() и save(),точнее какие данные получать и как. Можно пример кода класса Persistent?
load()должен загружать данные сессии из таблицы, используя идентификатор сессии ($sid) в качестве ключа (SELECT session_data FROM session WHERE session_id = '{$sid}').save(), соответственно, сохранять эти данные в базу.PS — того кода, к сожалению, не осталось
Поэтому при сохранении сессии важно знать, является ID новым или нет (если ID новый, то для сохранения будет использоваться INSERT, если существующий — то UPDATE; по большому счету можно обойтись одним REPLACE, однако это не особо эффективное решение).
А почему бы здесь использовать завернутый в специально обученную функцию INSERT … ON DUPLICATE KEY UPDATE? Понятно, что такой синтаксис только для mysql, но другие СУБД не всем нужны и там такой функционал тоже вполне реализуем, если не ошибаюсь.