Статьи

Написание пользовательских обработчиков сессий

Сессии — это инструмент, который помогает веб-программисту преодолеть безликую природу интернета. Вы можете использовать их для создания корзин покупок, отслеживания посещений веб-сайта и даже отслеживания навигации пользователя по вашему приложению. Поведение обработки сеансов по умолчанию в PHP может обеспечить все, что вам нужно, в большинстве случаев, но могут быть ситуации, когда вы захотите расширить функциональность и сохранить данные сеанса по-другому. Эта статья покажет вам, как работает функциональность по умолчанию, а затем покажет, как переопределить ее для предоставления настраиваемого решения.

Анатомия хранения сессий

Перед реализацией пользовательского обработчика сохранения сеанса полезно понять, как PHP обычно хранит данные сеанса. Данные сохраняются в небольшом файле на сервере, который связан с уникальным идентификатором, который затем сохраняется в cookie на клиенте браузером. Если файлы cookie не используются, идентификатор обычно передается в качестве параметра в URL. Какой бы метод ни использовался, PHP извлекает данные сеанса в последующих запросах страницы, используя идентификатор сеанса. Вы можете проверить, как это работает, сначала определив, где PHP сохраняет данные сеанса, и изучите их содержимое. Вы можете проверить директиву session.save_pathphp.inisession_save_path()

 <?php
echo session_save_path();

Выходными данными будет путь к месту хранения данных сеанса. Вы можете изменить местоположение в php.inisession_save_path()

 <?php
session_save_path("/path/to/session/data");

Если вы хотите установить другой каталог для хранения данных сеанса, рекомендуется выбрать местоположение, находящееся за пределами корневого веб-каталога, поскольку это может снизить риск взлома сеанса. Просто убедитесь, что вы дали каталогу соответствующие разрешения для чтения и записи сервером.

Теперь, когда вы знаете, где хранятся данные сеанса, вы можете перейти к этому каталогу и найти отдельные файлы, в которых хранится информация, относящаяся к каждому активному сеансу. Обычно именем файлов является «sess_», за которым следует идентификатор сеанса, связанный с данными. Вы можете узнать, какой у вас идентификатор сессии, с помощью функции session_id()

 <?php
echo session_id();

Вывод — псевдослучайная строка из 32 символов, очень похожая на следующую:

  k623qubavm8acku19somu6ce1k0nb9aj 

Открыв файл sess_k623qubavm8acku19somu6ce1k0nb9aj Если вы хотите сохранить следующий массив в сеансе следующим образом:

 <?php
$arr = array("red", "blue"); 
$_SESSION["colors"] = $arr;

Содержимое файла будет выглядеть так:

  цветы | A: 2: {я: 0; s: 3: "красные"; я: 1; s: 4: "синий";} 

Данные кодируются почти так же, как функция serialize() Когда данные хранятся в сеансе, все они объединяются, сериализуются и, в случае механизма хранения сеансов по умолчанию в PHP, помещаются в файл. Когда вам нужно получить данные, сеанс десериализует данные для использования приложением.

Здесь следует помнить, что сеанс чтения и записи данных по умолчанию выполняется с помощью функций serialize()unserialize() То же самое будет справедливо, если вы измените способ хранения этих данных. Вы можете изменить место хранения данных, но не то, как они хранятся.

Жизненный цикл сессии

Когда вы запускаете или продолжаете сеанс с помощью session_start()$_SESSION Когда выполнение скрипта заканчивается, данные сохраняются обратно в файл. Поэтому, когда вы устанавливаете переменную сеанса, она не сохраняется сразу. Конечно, вы можете заставить сеанс сохранять данные, вызывая session_write_close()

session_set_save_handler() Требуется шесть аргументов, каждый из которых является обратным вызовом, который обрабатывает определенную стадию жизненного цикла сеанса. Они есть:

  1. Открытие файла сеанса
  2. Закрытие файла сеанса
  3. Чтение данных сеанса
  4. Запись данных сеанса
  5. Уничтожение сессии
  6. Сборка мусора из файла сессии и данных

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

  Предупреждение: session_set_save_handler (): аргумент 1 не является допустимым обратным вызовом 

Обратные вызовы могут быть определены любым способом, который позволяет PHP, поэтому они могут быть прямыми функциями, замыканиями, объектными методами или методами статического класса.

 <?php
session_set_save_handler("open", "close", "read", "write", "destroy", "garbage");

В этом примере я предположил, что функции open()close()read()write()destroy()garbage()session_set_save_handler()

Создание пользовательского обработчика сохранения сеанса

Чтобы продемонстрировать каждую функцию обратного вызова, я переопределю поведение обработки сеанса по умолчанию, чтобы вместо этого сохранить их в базе данных MySQL. Базовая схема для таблицы должна включать поле для идентификатора сеанса, поле для данных и поле для определения времени последнего обращения к сеансу.

  CREATE TABLE сеанс ( 
     session_id CHAR (32) NOT NULL, 
     session_data ТЕКСТ НЕ НУЛЬ, 
     session_lastaccesstime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, 
     ПЕРВИЧНЫЙ КЛЮЧ (session_id)
 ); 

Стандартный метод генерации идентификатора сеанса использует алгоритм MD5, который генерирует 32-символьную строку, и поэтому я установил в поле session_idCHAR(32) Данные, хранящиеся в сеансе, могут быть неограниченными, но рекомендуется не злоупотреблять этим средством. Поле времени доступа может иметь тип TIMESTAMP

Открытие сессии

Первым этапом, через который проходит сеанс, является открытие файла сеанса. Здесь вы можете выполнить любое действие, которое вам нравится; в документации PHP указано, что эту функцию следует рассматривать как конструктор, поэтому вы можете использовать ее для инициализации переменных класса, если вы используете подход ООП. Обратный вызов принимает два аргумента, которые передаются автоматически — путь сохранения и имя сеанса.

 <?php
function open($path, $name) {
    $db = new PDO("mysql:host=myhost;dbname=mydb", "myuser", "mypassword");

    $sql = "INSERT INTO session SET session_id =" . $db->quote($sessionId) . ", session_data = '' ON DUPLICATE KEY UPDATE session_lastaccesstime = NOW()";
    $db->query($sql);    
}

Пример реализации здесь создаст запись в базе данных для хранения данных. Если запись сеанса события уже существует, сеанс session_last_accesstime Временная метка используется для обеспечения того, чтобы сеанс был «живым»; позже он используется для сборки мусора для очистки устаревших сессий, о которых я расскажу позже.

Чтение сессии

Сразу после открытия сеанса содержимое сеанса считывается из любого хранилища, которое вы назначили, и помещает его в массив $_SESSION Обратный вызов принимает один аргумент — идентификатор сеанса, который позволяет идентифицировать сеанс, который читается. Обратный вызов должен вернуть строку сериализованных данных, поскольку PHP затем будет десериализовать ее, как обсуждалось ранее. Там нет необходимости обслуживать это в вашем коде. Если для этого сеанса нет данных сеанса, вы должны вернуть пустую строку.

 <?php
function read($sessionId) { 
    $db = new PDO("mysql:host=myhost;dbname=mydb", "myuser", "mypassword");

    $sql = "SELECT session_data FROM session where session_id =" . $db->quote($sessionId);
    $result = $db->query($sql);
    $data = $result->fetchColumn();
    $result->closeCursor();

    return $data;
}

Важно понимать, что эти данные не извлекаются каждый раз, когда вы обращаетесь к переменной сеанса. Он извлекается только в начале жизненного цикла сеанса, когда PHP вызывает обратный вызов open, а затем обратный вызов read.

Запись на сессию

Запись данных обратно в любое хранилище, которое вы используете, происходит либо в конце выполнения скрипта, либо когда вы вызываете session_write_close() Обратный вызов получает два аргумента: данные для записи и идентификатор сеанса. Полученные данные уже будут сериализованы PHP.

 <?php
function write($sessionId, $data) { 
    $db = new PDO("mysql:host=myhost;dbname=mydb", "myuser", "mypassword");

    $sql = "INSERT INTO session SET session_id =" . $db->quote($sessionId) . ", session_data =" . $db->quote($data) . " ON DUPLICATE KEY UPDATE session_data =" . $db->quote($data);
    $db->query($sql)
}

Существует множество способов обработки данных, которые передаются в обратные вызовы чтения и записи. PHP передает данные в сериализованном виде в функцию записи и ожидает, что они будут сериализованы обратно из функции чтения, но это не значит, что вы должны хранить их таким образом. Вы можете сразу же десериализовать данные в обратном вызове записи, а затем выполнить какое-либо действие, зависящее от данных, или сохранить их по своему желанию. То же самое относится к обратному вызову чтения. Все эти решения зависят от реализации, и вам решать, что лучше для вашей ситуации.

Закрытие сессии

Закрытие сеанса происходит в конце жизненного цикла сеанса, сразу после записи данных сеанса. В этот обратный вызов не передаются никакие параметры, поэтому, если вам нужно обработать что-то определенное для сеанса, вы можете вызвать session_id()

 <?php
function close() {
    $sessionId = session_id();
    //perform some action here
}

Уничтожение сессии

Уничтожение сеанса вручную имеет важное значение, особенно при использовании сеансов в качестве способа защиты разделов вашего приложения. Обратный вызов вызывается, когда вызывается функция session_destroy() Идентификатор сеанса передается в качестве параметра.

 <?php
function destroy($sessionId) {
    $db = new PDO("mysql:host=myhost;dbname=mydb", "myuser", "mypassword");

    $sql = "DELETE FROM session WHERE session_id =" . $db->quote($sessionId); 
    $db->query($sql);

    setcookie(session_name(), "", time() - 3600);
}

В своей функции обработки сеансов по умолчанию функция session_destroy()$_SESSION В документации на php.net говорится, что любые глобальные переменные или файлы cookie (если они используются) не будут очищены, поэтому, если вы используете пользовательский обработчик сеансов, вы можете выполнять эти задачи и в этом обратном вызове.

Вывоз мусора

Обработчик сеанса должен учитывать тот факт, что у программиста не всегда будет возможность вручную уничтожить данные сеанса. Например, вы можете уничтожить данные сеанса, когда пользователь выходит из системы, и он больше не нужен, но нет гарантии, что пользователь будет использовать функцию выхода из системы для запуска удаления. Временами обратный вызов сборки мусора будет вызываться PHP для очистки устаревших данных сеанса. Параметр, который передается здесь, представляет собой максимальное время жизни сеанса, которое представляет собой целое число, детализирующее количество секунд, которое охватывает время жизни.

 <?php
function gc($lifetime) {
    $db = new PDO("mysql:host=myhost;dbname=mydb", "myuser", "mypassword");

    $sql = "DELETE FROM session WHERE session_lastaccesstime < DATE_SUB(NOW(), INTERVAL " . $lifetime . " SECOND)";
    $db->query($sql);
}

Сборка мусора выполняется случайным образом с помощью PHP. Вероятность запуска сборки мусора определяется с помощью директив php.inisession.gc_probabilitysession.gc_divisor Если вероятность установлена ​​на 1, а делитель на 100, например, сборщик мусора с вероятностью 1% запускается при каждом запросе (1/100).

В примере обратного вызова используется максимальное время жизни сеанса для сравнения времени последнего обращения к сеансу. Если срок жизни сеанса был превышен, он удаляет сеанс и все данные, относящиеся к нему.

Резюме

Изменение поведения по умолчанию при обработке сеансов PHP может быть полезно во многих ситуациях. В этой статье показано, как PHP обрабатывает данные сеанса «из коробки» и как их можно изменить в соответствии с потребностями вашего собственного приложения, сохраняя данные в базе данных MySQL. Конечно, вы можете выбрать свою любимую базу данных или другое решение, такое как XML, Memcache или другую файловую систему. Десериализация данных до их записи позволит вам хранить данные по своему усмотрению, просто помните, что вы должны отправить данные обратно в сериализованную форму, чтобы PHP мог использовать их при чтении.

Изображение через Сергея Миронова / Shutterstock