Web - Сессии PHP. Часть 4. - PRCY⮭net
Хранилище. Создание собственного обработчика.

Опять возвращаюсь к хранению данных сессии.

В этот раз я расскажу, как сделать свой обработчик, как его активировать, и какие подводные камни я нашел.
Работа с данными сессии разделена на 6 разных этапов. Соответственно, и обработчик сессии должен перехватить все 6 функций. Вот их условные названия: open, read, write, delete, gc (garbage collection) и close. Я специально не поставил круглые скобки после имен функций, чтоб они в тексте не путались со встроенными функциями PHP.
Рассмотрим их работу подробнее:

* open.
Назначение этой функции - подготовка ко всем остальным операциям. Функция вызывается автоматически сразу после старта сессии. В параметрах ей передаются путь к директории, где предполагается хранить файлы с данными сессий, и имя сессии.
Путь берется из php.ini ("session.save_path") либо устанавливается в программе функцией session_save_path().
Имя сессии может использоваться для составления имени файла с данными, а можно его просто игнорировать - вероятность повтора строки MD5 крайне мала.
В литературе встречается мнение, что если данные сессии хранятся в базе данных, то в этой функции можно произвести подключение к БД. Я считаю, если БД есть и используется, то не только для хранения данных сессии, и подключение можно произвести раньше, в основной программе. Так проще и понятней.
* read.
Назначение - считать данные сессии. Функция вызывается сразу после open. Единственный параметр - идентификатор сессии. Вернуть функция должна строку, соответствующую формату входного параметра функции unserialize(). Если данных нет, то она должна вернуть пустую строку.
В этой функции также важно понять, есть ли источник данных, т.к. сессия могла быть ранее уничтожена сборщиком мусора (gc) или функцией delete. Также здесь можно проверять и устанавливать блокировки (об этом расскажу в последующих частях).
* write.
Назначение - запись данных сессии. Функция автоматически вызывается при следующих обстоятельствах: завершение сессии по желанию программиста и по завершению программы (когда вывод в браузер окончен и сообщения об ошибках отобразить в браузере уже нельзя). Входных параметров два: идентификатор сессии и сохраняемые данные в сериализованном виде. Возвращаемое значение должно символизировать результат работы, но и отрицательный результат уже ни на что не повлияет, если вывод завершен (данные не сохранены, работа окончена, моем руки).
Не стоит надеяться, что раз источник данных был доступен функции read, то в write не надо проверять его доступность. Файл (или запись в БД) вполне могли уже удалить. По крайней мере, есть такая вероятность, и проверки будут не лишними. Правда, если в read отрыть файл, сохранить его дескриптор и не закрывать, то в write файл будет гарантированно существовать (в *nix системах он может быть еще удален, но физически писать в него еще можно), если конечно не додуматься до хранения временных файлов на сетевом диске или съемном носителе. (? delete)
Принудительная запись осуществляется функцией session_write_close(). Начиная с PHP 4.3.11, у нее есть более благозвучное имя - session_commit().
* delete.
Назначение - удалить данные сессии. Вызывается автоматически некоторыми встроенными функциями. Например, session_destroy() (с версии 4.3.3), или session_regenerate_id() (с версии 5.1.0). Входной параметр один - идентификатор сессии. Так стоит проверить, существует ли объект для удаления, или, по крайней мере, подавить вывод ошибок - в их обработке смысла нет.
* gc.
Назначение - "сборка мусора" (удаление ненужных данных). Разработчики PHP сделали предположение, что этот процесс может быть медленным, и поэтому эта функция вызывается не каждый раз. Периодичность запуска определяется настройками php.ini ("session.gc_divisor" и "session.gc_probability"): session.gc_probability/session.gc_divisor - это "вероятность" (chance) запуска сборщика мусора. Входной параметр - максимальное время жизни для сессии в секундах. Значение его берется из php.ini ("session.gc_maxlifetime"), либо может быть установлено в программе через ini_set(). Также можно игнорировать этот параметр и использовать свое значение.
Если данные сессий хранятся в индивидуальных файлах, то рекомендуется в проверке использовать время изменения файла, т.к. в ОС Windows может быть неправильная обработка времени доступа. Например, как утверждается на сайте www.php.net, Windows98 не сохраняет время доступа, а только дату. Запускать web-сервер под Windows98 - это, на сегодняшний день, выглядит странно, но береженого Бог бережет - лучше использовать время модификации.
С другой стороны, встроенный по умолчанию обработчик и так работает с файлами, и создавать свой аналогичный обработчик нет необходимости, если, конечно, он не выполняет каких-то особых действий, которые в штатном обработчике не предусмотрены.
* close.
Назначение - завершающие операции. Это уникальная функция, по сравнению с остальными пятью. Параметров - нет. Условия работы - особые: вывод окончен, все объекты разобраны. Т.е., на эту роль подходит либо глобальная функция, либо статический метод класса, а метод объекта недопустим. И самое главное: она, в общем-то, не нужна. Не отрицаю, что возможно есть и ей применение, но два наиболее распространенных хранилища - файлы и СУБД MySQL - прекрасно обходятся без этой функции. Файлы, если их по какой-либо причине не закрыли, закроются автоматически по завершении программы. То же произойдет с подключением к серверу базы данных.


В завершение этой части я привожу пример обработчика, который, подобно штатному, хранит данные в индивидуальных файлах. Для разнообразия и примера я его сделал в виде класса.
Код: (php)
<?php
class session_std
{
var $_path = '';
var $_fd = false;

function session_open($path, $name)
{
$this->_path = $path; // Сохраним путь - его нам больше не скажут
return true;
}

function session_close()
{
return true; // А делать-то и нечего
}

function session_read($sid)
{
$file = "$this->_path/$sid";
if (!file_exists($file)) // Файл был кем-то удален?
return "";
if (!($this->_fd = @fopen($file, 'rb+')))
return ""; // Не удалось открыть
$data = fread($this->_fd, filesize($file));
return $data; // Ничего с данными делать не надо - просто возвращаем
}

function session_write($sid, $data)
{
if ($this->_fd === false) // Если в session_read() не удалось открыть
{
if ($this->_fd = @fopen($file, 'wb')) // попытка создать файл
return false;
}
rewind($fd); // Мотаем на начало
ftruncate($fd, 0); // очистить файл
$res = fwrite($this->_fd, $data);
fclose($this->_fd);
return $res;
}

function session_delete($sid)
{
$file = "$this->_path/$sid";
if (!is_file($file))
return false;
return unlink($file); // Просто удаляем
}

function session_gc($lifetime)
{
if (!($dir = opendir($this->_path)))
return false;
while($file = readdir($dir))
{
$filepath = "$this->_path/$file";
if (!is_file($filepath)) // А вдруг это директория?
continue;
$time = filemtime($filepath); // Используем время изменения
$now = time();
if ($time + $lifetime < $now)
unlink($filepath);
}
closedir($dir);
return true; // Стало в доме меньше пыли
}
}

$storage = new session_std;

session_set_save_handler(
array(&$storage, 'session_open'),
array('session_std', 'session_close'), // См. описание close
array(&$storage, 'session_read'),
array(&$storage, 'session_write'),
array(&$storage, 'session_delete'),
array(&$storage, 'session_gc')
);

session_save_path("/tmp/my_session_dir/"); // Теперь мусорим сюда
session_name("MY_SESSION_ID"); // Имя у сессии будет такое

session_start();
?>

Пока все.
Продолжение - в следующей статье.

Роман Чернышов (RXL)
Information
  • Posted on 31.01.2010 22:12
  • Просмотры: 1055