Статья
Версия для печати
Обсудить на форуме
Сессии PHP. Часть 3.


Подготовка данных к сохранению.

В прошлых частях речь шла только о том, как использовать сессию и как ее идентифицировать. Теперь я расскажу, как хранятся данные сессии, которые мы помещаем в массив $_SESSION.
Данные хранятся на стороне сервера и об их наличии и составе пользователь может только догадываться.
В программе эти данные могут состоять из любых типов данных, доступных программисту: строки, числа, массивы и объекты, но не ресурсы (об этом ниже).
При записи данные подвергаются процедуре сериализации (serialization) - преобразования структур любой сложности (не содержащих ресурсов) в строку, из которой в последствии можно восстановить структуру назад. Для сериализации используется функция serialize(). Для обратного процесса - десериализации - unserialize(). При старте сессии эти PHP, получив от обработчика сессии строку, посредством функции unserialize() восстанавливает данные и помещает их в массив $_SESSION. При завершении программы происходит обратный процесс: массив $_SESSION автоматически сериализуется и строка передается обработчику сессии для записи. Т.е. обработчик сессии работает только со строкой и не зависит от сложности структуры хранимых данных.
Хотя процедура проходит автоматически, в ней то же есть свои тонкости и ограничения.
Рассмотрим особенности работы сериализации:

1. Ссылки.
Если Вы не знакомы с работой ссылок в PHP, то почитайте обучающую литературу или почитайте замечательный онлайновый справочник: http://www.php.net/manual/en/language.references.php .
Массив $_SESSION, как и любой другой массив, может содержать ссылки на другие переменные, а так же может содержать объекты и массивы, которые тоже содержат ссылки. Возможна да же рекурсивная связь.

Код: (PHP)
<?php

$array['b'] = array();
$array['a'] = array('bRef' => &$array['b']);
$array['b']['aRef'] = &$array['a'];

echo "<pre>" . htmlspecialchars(print_r($array, true)) . "</pre>";

?>

Можно сериализовать массив целиком и потом восстановить его. Но если попытаться сериализовать отдельно $array['a'] и $array['b'], а потом их восстановить, то связи будут нарушены.
Вот пример такого разрушения связей. Для наглядности я добавил элемент массива $array['a']['val'].

Код: (PHP)
<?php

$array['b'] = array();
$array['a'] = array('bRef' => &amp;$array['b']);
$array['b']['aRef'] = &$array['a'];
$array['a']['val'] = 1;

echo "<pre>" . htmlspecialchars(print_r($array, true)) . "</pre>";

$str_a = serialize($array['a']);
$str_b = serialize($array['b']);

echo "<pre>" . htmlspecialchars($str_a) . "</pre>";
echo "<pre>" . htmlspecialchars($str_b) . "</pre>";

$array2['a'] = unserialize($str_a);
$array2['b'] = unserialize($str_b);
$array2['a']['val'] = 2;

echo "<pre>" . htmlspecialchars(print_r($array2, true)) . "</pre>";

?>

Аналогично выглядит проблема ссылок в объектах. Как раз такой случай описан здесь: http://ru.php.net/manual/en/function.serialize.php#60317 .

2. Объекты и их классы.
Можно сериализовать любой объект, сохранить его в хранилище и в следующий раз изъять и восстановить. В отличие от массивов объекты помимо данных содержат информацию о своем классе, методы и прочую "нагрузку". Особенность сериализации в том, что сохраняется только состояние объекта - его данные, а для "оживления" обязательно заранее должен быть объявлен класс.
Выходов из этого положения два: заранее загружать объявления необходимых классов, либо воспользоваться специальным обработчиком, который сможет попытаться подгрузить требуемый класс. Назначается обработчик в ini.php (unserialize_callback_func'), но может быть изменен во время работы программы.
Выглядит этот обработчик примерно так:

Код: (PHP)
<?php

ini_set("unserialize_callback_func", "my_unserialize_callback_func");

function my_unserialize_callback_func($classname)
{
  // Предпологаем, что файлы, содержащие классы,
  //  находятся в поддиректории classes и имеют имя
  //  соответствующее имени класса.
    $filename = "classes/$classname.php";
    if (!is_file($filename))
        die("Не удалось подгрузить класс '$classname'");
    include_once($filename);
}

?>

На текущий момент обработчиком может быть только функция, но не метод объекта или класса.

3. Ресурсы, контроль сериализации/десериализации объектов.
Бывает так, что не все данные объекта имеет смысл сохранять, а то и вообще не возможно. Например, дескриптор открытого файла и подключение базы данных относятся к типу ресурсов и не могут быть сериализованы. Если бы их можно было бы все-таки сохранить, то после восстановления они потеряли бы смысл, т.к. не были бы связаны ни с отрытым файлом, ни с сервером базы данных.
Для того чтобы можно было управлять процессом сериализации/десериализации в классах PHP предусмотрены специальные "волшебные" методы: __sleep() и __wakeup(). Оба метода не обязательны.
Метод __sleep() должен возвращаться массив с именами своих свойств, которые будут сохранены.
Метод __wakeup() нужен для выполнения процедур инициализации объекта после десериализации.
Подробнее можно прочесть здесь: http://www.php.net/manual/en/language.oop.magic-functions.php

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

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

Роман Чернышов (RXL) 26.03.2006
Версия для печати
Обсудить на форуме