Хранение сессии в базе данных
Вступление
Несмотря на то, что стандартный способ хранения данных сессии подходит для большинства задач, с которыми могут столкнутся php-разработчики, иногда вам всё же придётся искать альтернативу. Наиболее частыми причинами для хранения данных сессии в базе данных являются:
-
Ваш сайт разбит на несколько частей которые храняться на нескольких серверах и чтобы быть увереным, что сессия работает правильно вам необходимо хранить данные сессии в базе данных, общей для всех серверов.
-
Ваш сайт находиться на одном сервере с другими сайтами и вы хотите избежать проблем безопасности, которые с этим связаны.
-
Ваш сайт имеет очень большие запросы к скорости работы и поэтому вам требуется более быстрый способ хранения данных сессии. Существует множества методик по ускорению чтения из базы данных которые могут быть использованы в данном случае.
К счастью, PHP делает эту задачу простой.
Хранение данных сессии.
Прежде чем вы сможете хранить данные сессии в базе данных вам необходимо создать таблицу. Вот пример для MySQL:
CREATE TABLE sessions (
id varchar(32) NOT NULL,
access int(10) unsigned,
data text,
PRIMARY KEY (id)
);
Это самый простой пример, вы можете изменить таблицу по своему усмотрению, чтобы хранить, например, время создания сессии или уникальный идентификатор для повышения безопасности.
После того как вы создали таблицу, давайте посмотрим, как её использовать.
session_set_save_handler()
PHP имеет встроенную функцию, которая позволяет изменить механизм сессии, заданный по умолчанию. С её помощью вы можете задавать свои функции для различных задач механизма сессии. Эта статься организована в соответствии со списком функций, которые вам будет необходимо написать. Для каждой функции, я покажу вам пример (используя MySQL) и все подробно объясню.
Функция session_set_save_handler() принимает шесть параметров, каждый из которых – это имя функции, которую вам придется написать. Эти функции будут отвечать за следующие задачи:
-
Открытие места хранения данных сессии
-
Закрытие места хранения данных сессии
-
Чтение данных сессии
-
Запись данных сессии
-
Уничтожение всех данных сессии
Удаление данных предыдущей сессии
Для удобства, в этой статье мы примем следующие имена для этих функций:
session_set_save_handler ('_open', '_close', '_read', '_write', '_destroy', '_clean');
эта строчка должна стоять до вызова session_start(), но сами функции вы можете объявить в любом месте.
Преимущество данного способа в том, что в коде вы можете продолжать делать всё тоже самое, как и при стандартном механизме хранения данных сесиии. Массив $_SESSION всё тот же и ведет себя точно также, php всё также генерирует и передаёт ключ сессии. Всё что вам необходимо сделать – это добавить одну строчку, указанную выше.
Также очень важно понять, что конкретно делают функции, на случай непредвиденных ошибок.
_open() и _close()
Эти функции взяимосвязаны, они используются для открытия и закрытия места хранения данных сессии соответственно. Если вы храните данные сессии в файловой системе, эти функции открывают и закрывают файлы (скорее всего вам понадобится глобальная переменная управления файлом, чтобы другие функции могли её использовать).
Поскольку вы используете базу данных, _open() и _close() могут быть настолько просты, как, например:
<?php
function _open()
{
mysql_connect('127.0.0.1', 'myuser', 'mypass');
mysql_select_db('sessions');
}
function _close()
{
mysql_close();
}
?>
Такой вариант имеет недостаток: когда переменная управления базой данных не создана для функций mysql_select_db() и mysql_query(), MySQL использует последнее содинение, то есть если где нибудь в коде сайта вы используете mysql_select_db чтобы выбрать другую базу данных, запись в сессию может не произойте, потому что последнее соединение будет с другой базой данных. Такую ошибку будет очень сложно отследить, несмотря на это, встречается она достаточно часто.
Избежать такой ошибки можно двумя способами: вы можете использовать отдельное соединение для записи данных сессии или вы можете взять привычку исползовать mysql_select_db() перед каждой функцией, которой необходима определенная база данных, это включает себя как механизм хранения сессии, так и код сайта. Это всё, конечно, при условии, что вы используете больше одной базы данных в своём коде.
В данной статье я буду использовать первый способ – использовать отдельное соединение для механизма сессии. Назовем эту переменную $_sess_db, и тогда код нащих функций будет выглядеть следующим образом:
<?php
function _open()
{
global $_sess_db;
$_sess_db = mysql_connect('127.0.0.1', 'myuser', 'mypass');
mysql_select_db('sessions', $_sess_db);
}
function _close()
{
global $_sess_db;
mysql_close($_sess_db);
}
?>
Я использую знак подчеркивания в начале имени переменной как указание, что данная переменная не должна изменяться в нигде в дальнейшем коде.
Я хочу внести ещё одно изменение. Большинство встроенных php функций возвращают TRUE если ошибок нет и FALSE если при выполнении возникла ошибка. Мы можем заставить наши функции делать тоже самое:
<?php
function _open()
{
global $_sess_db;
if ($_sess_db = mysql_connect('127.0.0.1', 'myuser', 'mypass')) {
return mysql_select_db('sessions', $_sess_db);
}
return FALSE;
}
function _close()
{
global $_sess_db;
return mysql_close($_sess_db);
}
?>
Перейдём к следующей функции.
_read()
Эта функция вызывается когда необходимо записать данные в сессию. Это происходит сразу после вызова _open() который, в свою очередь, вызывается с помощью session_start().
PHP передает в _read() идентификато сессии:
<?php
function _read($id)
{
global $_sess_db;
$id = mysql_real_escape_string($id);
$sql = “SELECT data FROM sessions WHERE id = '$id'”;
if ($result = mysql_query($sql, $_sess_db)) {
if (mysql_num_rows($result)) {
$record = mysql_fetch_assoc($result);
return $record['data'];
}
}
return ' ';
}
?>
Обработчик, который PHP использует для сериализации данных, задается session.serialize_handler параметром конфигурации php. По умолчанию он имеет значение php.
_write()
Функция _write() вызывается когда необходимо записать данные в сессию, обычно в самом конце скрипта.
PHP передает идентификатор сессии и данные сессии. Вы можете не волноваться о формате данных, поскольку php сериализует эти данные, они представляют из себя строку. Несмотря на это вам необходимо убедиться, что строка не содержит опасных элементов прежде чем использовать её в запросе.
<?php
function _write($id, $data)
{
global $_sess_db;
$acess = time();
$id = mysql_real_escape_string($id);
$access = mysql_real_escape_string($access);
$data = mysql_real_escape_string($data);
$sql = “REPLACE INTO sessions VALUES ('$id','$access','$data')”;
return mysql_query($sql, $_sess_db);
}
?>
Я использовал REPLACE, потому что так мы делаем тоже что и используя INSERT, но в случаях когда передаваемый индентификатор сессии уже существует, REPLACE удалит старую запись прежде чем записывать новую. Таким образом отпадает необходимость проверки на наличие в таблице записи с передваемым идентификатором сессии. Необходимо учесть что REPLACE работает в MySQL, но в других типах баз данных такой команды может не быть.
_destroy()
Функция _destroy() вызывается когда PHP необходимо уничтожить все данные текущей сессии. Самый очевидный пример, когда вызывается session_destroy().
PHP передаёт в функцию идентификатор сессии.
<?php
function _destroy($id)
{
global $_sess_db;
$id = mysql_real_escape_string($id);
$sql = “DELETE FROM sessions WHERE id = '$id'”;
return $result = mysql_query($sql, $_sess_db);
}
?>
Функция _destroy() стирает лишь запись в базе данных, не затрагивая массив $_SESSION.
_clean()
Функция _clean() вызывается периодически для удаления старых записей в таблице сессий. Точнее, как часто эта функция вызывается установлено двумя параметрами конфигурации php: session.gc_probability и session.gc_divisor. Их значения по умолчанию 1 и 100 соответственно, что означает что вероятность вызова функции _clean() за сессию равно 1 поделить на 1000 = 0.1%.
Так как функция _write() записывает в таблицу для каждой записи точное время последнего доступа в колонку access, это может быть использовано чтобы определить какие записи удалять. PHP передаёт максимальное количество секунд после которых сессиия считается просроченной:
<?php
function _clean($max)
{
global $_sess_db;
$old = time() - $max;
$old = mysql_real_escape_string($old);
$sql = “DELETE FROM sessions WHERE access < '$old' ”;
return $result = mysql_query($sql, $_sess_db);
}
?>
Количество секунд которые PHP передаёт в эту функцию – это значение параметра session.gc_maxlifetime из конфигурации PHP. Вы можете изменять это значение в случае необходимости.
Итог
Теперь у вас есть все необходимые инстременты чтобы изменять механизм хранения данных сессии. Так же, я надеюсь, вы получили лучшее понимание предназначения этих шести функций и готовы решить любые проблемы, которые могут появиться в процессе.
Автор Chris Shiflett
Перевод Александр Белковец
|