Самоучитель PHP 4 - Котеров Д. В.
.pdf
348 |
Часть IV. Стандартные функции PHP |
Регистрация переменных
bool session_register(mixed $name [, mixed $name1, ...])
PHP узнает о том, что ту или иную переменную нужно сохранить в сессии, если ее
предварительно зарегистрировать. Для этого и предназначена функция session_register(). Она принимает в параметрах одно или несколько имен переменных (имена задаются в строках, без знака $ слева), регистрирует их в текущей запущенной сессии и возвращает значение "истина", если все прошло корректно.
Почему же тогда я описал типы параметров как mixed, а не как string? Да потому, что на самом деле в функцию можно передавать не одну строку в ка- ждом параметре, а сразу список строк. Каждая такая строка будет регистриро- вать отдельную переменную с соответствующим именем. Более того — эле- ментом списка может опять же быть список строк, и т. д.
Нет ничего страшного, если мы дважды зарегистрируем одну и ту же переменную в сессии. На самом деле, чаще всего как раз так и происходит — при повторном запуске сценария. Вот пример:
Листинг 25.1. Пример работы с сессиями
<? session_start();
session_register("count"); $count=@$count+1;
?>
<body> <h2>Счетчик</h2>
В текущей сессии работы с браузером Вы открыли эту страницу <?=$count?> раз(а). Закройте браузер, чтобы обнулить счетчик. </body>
Как видим, все предельно просто.
Идентификатор сессии и имя группы
Что же, теперь мы уже можем начать писать кое-какие сценарии. Но вскоре возникнет небольшая проблема. Дело в том, что на одном и том же сайте могут сосущество-
Глава 25. Управление сессиями |
349 |
вать сразу несколько сценариев, которые нуждаются в услугах поддержки сессий PHP. Они "ничего не знают" друг о друге, поэтому временные хранилища для сессий должны выбираться не только на основе идентификатора пользователя, но и на основе того, какой из сценариев запросил обслуживание сессии.
Имя группы сессий
Что, не совсем понятно? Хорошо, тогда рассмотрим пример. Пусть разработчик A написал сценарий счетчика, приведенный в листинге 25.1. Он использует переменную $count, и не имеет никаких проблем. До тех пор, пока разработчик B, ничего не знающий о сценарии A, не создал систему статистики, которая тоже использует сессии. Самое ужасное, что он также регистрирует переменную $count, не зная о том, что она уже "занята". В результате, как всегда, страдает пользователь: запустив сначала сценарий разработчика B, а потом — A, он видит, что данные счетчиков перемешались. Непорядок!
Нам нужно как-то разграничить сессии, принадлежащие одному сценарию, от сессий, принадлежащих другому. К счастью, разработчики PHP предусмотрели такое положение вещей. Мы можем давать группам сессий непересекающиеся имена, и сценарий, знающий имя своей группы сессии, сможет получить к ней доступ. Вот теперь-то разработчики A и B могут оградить свои сценарии от проблем с пересечениями имен переменных. Достаточно в первой программе указать PHP, что мы хотим использовать группу с именем, скажем, sesA, а во второй — sesB.
string session_name([string $newname])
Эта функция устанавливает или возвращает имя группы сессии, которая будет использоваться PHP для хранения зарегистрированных переменных. Если $newname не задан, то возвращается текущее имя. Если же этот параметр указан, то имя группы будет изменено на $newname, при этом функция вернет предыдущее имя.
Session_name() лишь сменяет имя текущей группы и сессии, но не создает новую сессию и временное хранилище! Это значит, что мы должны в большин- стве случаев вызывать session_name(имя_группы) еще до ее инициализа- ции — вызова session_start(), в противном случае мы получим совсем не то, что ожидали.
Если функция session_name() не была вызвана до инициализации, PHP будет использовать имя по умолчанию — PHPSESID.
Кстати говоря, имя группы сессий, устанавливаемое рассматриваемой функ- цией, — это как раз имя того самого Cookie, который посылается в браузер клиента для его идентификации. Таким образом, пользователь может одно- временно активизировать две и более сессий — с точки зрения PHP он будет
350 |
Часть IV. Стандартные функции PHP |
менно активизировать две и более сессий — с точки зрения PHP он будет вы- глядеть как два ли более различных пользователя. Однако не забывайте, что, случайно установив в сценарии Cookie, имя которого совпадает с одним из имен группы сессий, вы "затрете" Cookie.
Вот простой пример применения этой функции.
<? session_name("CounterScript" session_start(); session_register("count"); $count=@$count+1;
?>
В текущей сессии Вы открыли эту страницу <?=$count?> раз(а).
Рекомендую всегда указывать имя группы сессии вручную, не полагаясь на значение по умолчанию. За это вам скажут спасибо разработчики других сценариев, когда они захотят использовать вашу программу вместе со своими.
Идентификатор сессии
Мы уже говорили с вами, зачем нужен идентификатор сессии (SID). Фактически, он является именем временного хранилища, которое будет использовано для хранения данных сессии между запусками сценария. Итак, один SID — одно хранилище. Нет SID, нет и хранилища, и наоборот.
В этом месте очень легко запутаться. В самом деле, как же соотносится идентификатор сессии и имя группы? А вот как: имя — это всего лишь собирательное название для нескольких сессий (то есть, для многих SID), запущенных разными пользователями. Один и тот же клиент никогда не будет иметь два различных SID в пределах одного имени группы. Но его браузер вполне может работать (и часто работает) с несколькими SID, расположенными логически в разных "пространствах имен".
Итак, все SID уникальны и однозначно определяют сессию на компьютере, выполняющем сценарий — независимо от имени сессии. Имя же задает "пространство имен", в которое будут сгруппированы сессии, запущенные разными пользователями. Один клиент может иметь сразу несколько активных пространств имен (то есть несколько имен групп сессий).
string session_id([string $sid])
Функция возвращает текущий идентификатор сессии SID. Если задан параметр $sid, то у активной сессии изменяется идентификатор на $sid. Делать это, вообще говоря, не рекомендуется.
Фактически, вызвав session_id() до session_start(), мы можем подключиться к любой (в том числе и к "чужой") сессии на сервере, если знаем ее идентификатор.
Глава 25. Управление сессиями |
351 |
Мы можем также создать сессию с угодным нам идентификатором, при этом автоматически установив его в Cookies пользователя. Но это — не лучшее решение, — предпочтительнее переложить всю "грязную работу" на PHP.
Другие функции
Здесь мы для полноты картины рассмотрим функции для работы с сессиями, которые применяются гораздо реже, чем уже описанные.
bool session_is_registered(string $name)
Функция session_is_registered() возвращает значение true, если переменная с именем $name была зарегистрирована в сессии, иначе возвращается false.
bool session_unregister(struing $name)
Эта функция отменяет регистрацию для переменной с именем $name для текущей сессии. Иными словами, при завершении сценария все будет выглядеть так, словно переменная с именем $name и не была никогда зарегистрирована. Возвращает true, если все прошло успешно, и false — в противном случае.
После вызова функции session_unregister() глобальная переменная, ко- торая была "аннулирована", не уничтожается, а сохраняет свое значение.
void session_unset()
Функция session_unset(), в отличие от session_unregister(), не только отме-
няет регистрацию переменных (кстати говоря, всех переменных сессии, а не какой-то одной), но и уничтожает глобальные переменные, которые были зарегистрированы в сессии.
string session_save_path([string $path])
Эта функция возвращает имя каталога, в котором будут помещаться файлы — временные хранилища данных сессии. В случае, если указан параметр, как обычно, активное имя каталога будет переустановлено на $path. При этом функция вернет предыдущий каталог.
К сожалению, функции, которая бы возвращала список всех зарегистрированных в сессии переменных, почему-то нет. Во всяком случае, в PHP версии 4.0.3.
Установка обработчиков сессии
До сих пор мы с вами пользовались стандартными обработчиками сессии, которые PHP использовал каждый раз, когда нужно было сохранить или загрузить данные из временного хранилища. Возможно, они вас не устроят — например, вы захотите хра-
352 |
Часть IV. Стандартные функции PHP |
нить переменные сессии в базе данных или еще где-то. В этом случае достаточно будет переопределить обработчики своими собственными функциями, и вот как оно делается.
Обзор обработчиков
Всего существует 6 функций, связанных с сессиями, которые PHP вызывает в тот или иной момент работы механизма обработки сессий. Им передаются различные параметры, необходимые для работы. Сейчас я перечислю все эти функции вместе с их описаниями.
bool handler_open(string $save_path, string $session_name)
Функция вызывается, когда вызывается session_start(). Обработчик должен взять на себя всю работу, связанную с открытием базы данных для группы сессий с именем $session_name. В параметре $save_path передается то, что было указано при вызове session_save_path() или же путь к файлам-хранилищам данных сессий по умолчанию. Возможно, если вы используете базу данных, этот параметр будет бесполезным.
bool handler_close()
Этот обработчик вызывается, когда данные сессии уже записаны во временное хранилище и его нужно закрыть.
string handler_read(string $sid)
Вызов обработчика происходит, когда нужно прочитать данные сессии с идентификатором $sid из временного хранилища. Функция должна возвращать данные сессии в специальном формате, который выглядит так:
имя1=значение1;имя2=значение2;имя3=значение3;...;
Здесь имяN задает имя очередной переменной, зарегистрированной в сессии, а значениеN — результат вызова функции Serialize() для значения этой переменной. Например, запись может иметь следующий вид:
foo|i:1;count|i:10;
Она говорит о том, что из временного хранилища были прочитаны две целые переменные, первая из которых равна 1, а вторая — 10.
string handler_write(string $sid, string $data)
Этот обработчик предназначен для записи данных сессии с идентификатором $sid во временное хранилище — например, открытое ранее обработчиком handler_open(). Параметр $data задается в точно таком же формате, который был описан выше. Фактически, чаще всего действия этой функции сводятся к записи в базу данных строки $data без каких-либо ее изменений.
bool handler_destroy(string $sid)
Глава 25. Управление сессиями |
353 |
Обработчик вызывается, когда сессия с идентификатором $sid должна быть уничтожена.
bool handler_gc(int $maxlifetime)
Данный обработчик — особенный. Он вызывается каждый раз при завершении работы сценария. Если пользователь окончательно "покинул" сервер, значит, данные сессии во временном хранилище можно уничтожить. Этим и должна заниматься функция handler_gc(). Ей передается в параметрах то время (в секундах), по прошествии которого PHP принимает решение о необходимости "почистить перышки", или "собрать мусор" (garbage collection) — т. е., это максимальное время существования сессии.
Как же должна работать рассматриваемая функция? Очень просто. Например, если мы храним данные сессии в базе данных, мы просто должны удалить из нее все записи, доступ к которым не осуществлялся более, чем $maxlifetime секунд. Таким образом, "застарелые" временные хранилища будут иногда очищаться.
На самом деле обработчик handler_gc() вызывается не при каждом запуске сценария, а только изредка. Когда именно — определяется конфигурационным параметром session.gc_probability. А именно, им задается (в процен- тах), какова вероятность того, что при очередном запуске сценария будет вы- бран обработчик "чистки мусора". Сделано это для улучшения производитель- ности сервера, потому что обычно сборка мусора — довольно ресурсоемкая задача, особенно если сессий много.
Регистрация обработчиков
Вы, наверное, обратили внимание, что при описании обработчиков я указывал их имена с префиксом handler. На самом деле, это совсем не является обязательным. Даже наоборот — вы можете давать такие имена своим обработчикам, какие только захотите.
Но возникает вопрос: как же тогда PHP их найдет? Вот для этого и существует функция регистрации обработчиков, которая говорит интерпретатору, какую функцию он должен вызывать при наступлении того или иного события.
void session_set_save_handler($open,$close,$read,$write,$destroy,$gc)
Эта функция регистрирует подпрограммы, имена которых переданы в ее параметрах, как обработчики текущей сессии. Параметр $open содержит имя функции, которая будет вызвана при инициализации сессии, а $close — функции, вызываемой при ее закрытии. В $read и $write нужно указать имена обработчиков, соответственно, для чтения и записи во временное хранилище. Функция с именем, заданным в
354 |
Часть IV. Стандартные функции PHP |
$destroy, будет вызвана при уничтожении сессии. Наконец, обработчик, определяемый параметром $gc, используется как сборщик мусора.
Эту функцию можно вызывать только до инициализации сессии, в противном случае она просто игнорируется.
Пример: переопределение обработчиков
Давайте напишем пример, который бы иллюстрировал механизм переопределения обработчиков. Мы будем держать временные хранилища сессий в подкаталоге sessiondata текущего каталога, и для каждого имени группы сессий создавать отдельный каталог.
Код листинга 25.2 довольно велик, но не сложен. Тут уж ничего не поделаешь — нам в любом случае приходится задавать все 6 обработчиков, а это выливается в "объемистые" описания.
Листинг 25.2. Переопределение обработчиков сессии
<?
//Возвращает полное имя файла временного хранилища сессии.
//В случае, если нужно изменить тот каталог, в котором должны
//храниться сессии, достаточно поменять только эту функцию function ses_fname($key)
{
return "sessiondata/".session_name()."/$key";
}
//Заглушки — эти функции просто ничего не делают
function ses_open($save_path, $ses_name) { return true; } function ses_close() { return true; }
// Чтение данных из временного хранилища function ses_read($key)
{
//Получаем имя файла и открываем файл $fname=ses_fname($key); $f=@fopen($fname,"rb"); if(!$f) return "";
//Читаем до конца файла
$st=fread($f,filesize($fname)); fclose($f);
return $st;
}
Глава 25. Управление сессиями |
355 |
// Запись данных сессии во временное хранилище function ses_write($key, $val)
{
$fname=ses_fname($key);
//Сначала создаем все каталоги (в случае, если они уже есть,
//игнорируем сообщения об ошибке)
@mkdir($d=dirname(dirname($fname)),0777); @mkdir(dirname($fname),0777);
// Создаем файл и записываем в него данные сессии $f=@fopen($fname,"wb"); if(!$f) return ""; fwrite($f,$val);
fclose($f); return true;
}
//Вызывается при уничтожении сессии function ses_destroy ($key)
{
return @unlink(ses_fname($key));
}
//Сборка мусора — ищем все старые файлы и удаляем их function ses_gc($maxlifetime)
{
$dir=ses_fname(".");
//Получаем доступ к каталогу текущей группы сессии $d=@opendir($dir); if(!$d) return false;
$DelDir=1; // Признак того, что каталог пуст, и его можно удалить
//Читаем все элементы каталога
while(($e=readdir($d))!==false) {
//Если это "точки", пропускаем их if($e=="."||$e=="..") continue;
//Файл слишком старый?
if(time()-filemtime($fname="$dir/$e")>=$maxlifetime) { @unlink($fname);
continue;
}
// Нашли не очень старый файл — значит, каталог точно
356 |
Часть IV. Стандартные функции PHP |
// не будет в результате работы пуст. $DelDir=0;
}
closedir($d);
//Если все файлы оказались слишком старые и удалены,
//удалить и каталог
if($DelDir) @rmdir($dir); return true;
}
//Регистрируем наши новые обработчики session_set_save_handler(
"ses_open", "ses_close", "ses_read", "ses_write", "ses_destroy", "ses_gc"
);
//Для примера подключаемся к группе сессий test session_name("test");
session_start(); session_register("count");
//Дальше как обычно...
$count=@$count+1; ?>
<body> <h2>Счетчик</h2>
В текущей сессии работы с браузером Вы открыли эту страницу <?=$count?> раз(а). Закройте браузер, чтобы обнулить этот счетчик. </body>
Сессии и Cookies
До сих пор я подразумевал, что использование сессий немыслимо без Cookies. Действительно, Cookies представляют собой наиболее элегантное и простое решение задачи идентификации каждого подключившегося пользователя, что необходимо для связи временного хранилища и данных сессии. Но как быть, если пользователи отключили Cookies в своих браузерах?
Глава 25. Управление сессиями |
357 |
|
|
|
|
|
|
|
К сожалению, пользователи отключают Cookies гораздо чаще, чем это может показаться на первый взгляд. Например, всего год назад Всероссийский Клуб Вебмастеров проводил опрос, в результате которого выяснилось, что количе- ство пользователей Интернета, отключивших у себя по каким-то соображениям поддержку Cookies, достигает 20—30%. Что это за соображения? Многие ду- мают, что Cookies потенциально являются "дырой" в безопасности их компью- тера. Это совершенно не соответствует действительности, потому что браузе- ры всегда имеют ограничения на количество и суммарный объем Cookies, которые могут быть в них установлены. Другие же просто не хотят, чтобы неиз- вестно кто писал что угодно на их жесткий диск. Правда, это не мешает таким "перестраховщикам" открывать пришедший по почте исполняемый файл — та- кой же, как из письма типа "Love letter"…
В общем, вы видите, что для абсолютной уверенности в работоспособности ваших сценариев на любом браузере нужен механизм, позволяющий отказаться от использования Cookies при управлении сессиями. Такой механизм действительно существует в PHP, и основная его идея состоит в том, чтобы передавать идентификатор сессии не в Cookies, а каким-нибудь аналогичным путем — например, в данных запроса GET. Последнее мы сейчас и рассмотрим.
Явное использование константы SID
В PHP существует одна специальная константа с именем SID. Она всегда содержит имя группы текущей сессии и ее идентификатор в формате имя=идентификатор. Вспомните: именно в таком формате данные принимаются, когда они приходят из Cookies браузера. Таким образом, нам достаточно просто-напросто передать значение константы SID в сценарий, чтобы он "подумал", будто бы данные пришли из Cookies. Вот пример:
Листинг 25.3. Sesget.php: простой пример использования сессий без Cookies
<? session_name("test"); session_start();
session_register("count"); $count=@$count+1;
?>
<body> <h2>Счетчик</h2>
В текущей сессии работы с браузером Вы открыли эту страницу
