Pull to refresh

Пишем свою реализацию сессий для обработки мертвой сессии перед зачисткой

Reading time 3 min
Views 4.9K
Мой первый хабратопик, надеюсь, что не последний.

Представим ситуацию: есть корзина покупок на сайте, при добавлении в корзину мы ставим на товар т.н. lock, исключающий его из списка доступных для покупки товаров. Когда клиент удаляет товар из корзины — lock снимается. Но что делать, если пользователь просто закрыл браузер? В таком случае сессия будет удалена сборщиком мусора, а локи так и останутся.

Когда я столкнулся с такой ситуацией, первое что мне пришло в голову — хранить локи и дату доступа в БД и периодически дергать её кроном. Но костыльность этого решения очевидна. А вот ещё бред, с которым я столкнулся при решении сабжа: для сериализации и десереализации сессий используются функции и формат, отличные от функций serialize и unserialize. Приходится делать велосипеды для ансериализации сессии.

Ближе к телу: как решил проблему я…
Тут надо сделать замечание, что идею для такого решения мне подал хабраюзер rigid, а помогли решить пару около-сабжевых проблем в конференции php@conference.jabber.ru.

PHP позволяет определять свои функции для обработки сессий. Отвечает за это функция session_set_save_handler. В качестве параметров она принимает список функций, который будут вызываться для работы с сессиями. В мануале есть даже пример, который реализует стандартный механизм работы с сессиями. Его-то мы и возьмем, изменив только функцию gc, которая занимается сборкой мусора, т.е. удалением файлов мертвых сессий.

Пример функции gc:

/* Функция принимает в качестве параметра время жизни сессии */
function gc($maxlifetime)
 {
    global $sess_save_path; /* путь, где лежат сессии */
    foreach (glob("$sess_save_path/sess_*") as $filename)
     {
        /* Проверяем не пора ли убить сессию */
        if (filemtime($filename) + $maxlifetime < time())
         {
            $tmp_sess=unserializesession(file_get_contents($filename)); /* $tmp_sess у нас теперь аналогична $_SESSION той сессии */
            /* Обрабатываем данные, например снимаем локи из этой сессии */
            @unlink($filename); /* Удаляем сессию */
         }
     }
    return true;
 }


* This source code was highlighted with Source Code Highlighter.


Код функции unserializesession, взятый откуда-то из интернета (скорее всего из комментариев к функции в мануале PHP):

function unserializesession($data) {
 $vars=preg_split('/([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff^|]*)\|/',
          $data,-1,PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
 for($i=0; $vars[$i]; $i++) $result[$vars[$i++]]=unserialize($vars[$i]);
 return $result;
}


* This source code was highlighted with Source Code Highlighter.


Теперь подключаем это в наш проект:

session_set_save_handler("open", "close", "read", "write", "destroy", "gc");
/* Вероятность чистки мусора на каждый session_start() примерно равна 30%, другими словами - чистка мертвых сессий будет производится при тридцати вызовах session_start() из ста */
ini_set("session.gc_probability", 30); /* Можно настроить на 100%, если у вас там нет никакого медленного кода */
ini_set("session.gc_divisor", 100);
ini_set("session.gc_maxlifetime", 1800); /* Время жизни сессии в секундах (то самое, которое передается в функцию gc) */
session_start();


* This source code was highlighted with Source Code Highlighter.


Есть одно но: в Debian/Ubuntu свой механизм очистки сессий, который выполняется кроном, а у PHP нет возможности удалять файлы сессий. Мне это не понравилось, т.к. ломает функционал PHP подменяя его своим сборщиком мусора для сессий. Решить проблему можно задав собственный каталог для файлов сессии и закрыв его в .htaccess (если он находится в document_root).

P.S. Честно говоря я не уверен, что мне можно было публиковать пост сразу в блог PHP. Я не активный пользователь хабра и не знаю местных порядков. Прошу не ругаться.
Tags:
Hubs:
+14
Comments 53
Comments Comments 53

Articles