Сессии в PHP
Автор: phpfaq.ru
Подробное описание работы и объяснение механизма работы с сессиями PHP.
Введение
Как устроены, и как работают
сессии?
Область
применения
Возможные проблемы и
их устранение
Дополнительная
информация
Полезные
Ссылки
Введение
Сессии - это на самом
деле очень просто. Надо только понимать, для чего они нужны и как
устроены. Ответим сначала на первый вопрос.
Возможно Вы знаете, что веб-сервер не
поддерживает постоянного соединения с клиентом, и каждый запрос обрабатывается,
как новый, без связи с предыдущими.
То есть, нельзя ни отследить
запросы от одного и того же посетителя, ни сохранить для него переменные между
просмотрами отдельных страниц. Вот для решения этих двух задач и были изобретены
сессии.
Собственно, сессии, если в двух словах - это механизм, позволяющий
однозначно идентифицировать браузер и создающий для этого браузера файл на
сервере, в котором хранятся переменные сеанса.
Подробно расписывать нужду
в таком механизме я не буду. Это такие случаи, как корзина
покупок в интернет магазине, авторизация, а так же, и не совсем тривиальные проблемы,
такие, например, как защита интерактивных частей сайта от спама.
В
принципе, довольно несложно сделать собственный аналог сессий, не такой
функциональный, как встроенный в PHP, но похожий по сути. На cookies и базе
данных.
При запросе скрипта смотрим, пришла ли cookies с определенным именем.
Если cookies нет, то ставим ее и записываем в базу новую строку с данными
пользователя. Если cookies есть, то читаем из базы данные. Еще одним запросом
удаляем из базы старые записи и вот у нас готов механизм сессий. Совсем
несложно. Но есть некоторые нюансы, которые делают предпочтительным
использование именно встроенного механизма сессий.
Как
устроены, и как работают сессии?
Для начала надо как-то
идентифицировать браузер. Для этого надо выдать ему уникальный идентификатор и
попросить передавать его с каждым запросом. Стыдно признаться, но когда я
впервые узнал о сессиях, я думал, что это какой-то особый механизм, некий новый
способ общения браузера с сервером - "сессии". Что идентификатор сессии
передается каким-то особым образом. Но, разочарование было жестоким...
Сессии
используют стандартные, хорошо известные способы передачи данных. Собственно,
других-то просто и нет.
Идентификатор - это обычная переменная. По умолчанию
ее имя - PHPSESSID.
Задача PHP отправить ее браузеру, чтобы тот вернул ее со
следующим запросом. Из уже упоминавшегося раздела FAQ ясно, что переменную можно
передать только двумя способами: в cookies или POST/GET запросом.
PHP
использует оба варианта.
За это отвечают две настройки в
php.ini:
session.use_cookies
- если
равно 1, то PHP передает идентификатор в cookies, если 0 - то нет.
session.use_trans_sid
если равно 1, то PHP передает
его, добавляя к URL и формам, если 0 - то нет.
Менять эти и другие параметры
сессий можно так же, как и другие настройки PHP - в файле php.ini, а так же с
помощью команды ini_set()
или в файлах настройки
веб-сервера
Если включена только первая, то при старте сессии (при
каждом вызове session_start()
) клиенту устанавливается cookies. Браузер
исправно при каждом следующем запросе эту cookies возвращает и PHP имеет
идентификатор сессии. Проблемы начинаются, если браузер cookies не возвращает. В
этом случае, не получая cookies с идентификатором, PHP будет все время стартовать
новую сессию, и механизм работать не будет.
Если включена только вторая,
то cookies не выставляется. А происходит то, ради чего, в основном, собственно, и
стоит использовать встроенный механизм сессий. После того, как скрипт выполняет
свою работу, и страница полностью сформирована, PHP просматривает ее всю и
дописывает к каждой ссылке и к каждой форме передачу идентификатора сессии. Это
выглядит примерно так:
<a
href="/index.php">Index</a>
превращается в
<a
href="/index.php?PHPSESSID=9ebca8bd62c830d3e79272b4f585ff8f">Index</a>
а
к формам добавляется скрытое поле
<input
type="hidden" name="PHPSESSID" value="00196c1c1a02e4c37ac04f921f4a5eec"
/>
И браузер при клике на любую ссылку, или при нажатии на
кнопку в форме, пошлет в запросе нужную нам переменную - идентификатор
сессии!
Теоретически, в наших с вами самодельных сессиях на cookies и базе,
можно самому, руками приписать ко всем ссылками передачу ид - и тогда наши
собственные сессии будут работать независимо от cookies. Но, согласитесь - приятнее,
когда эту работу делает кто-то другой? ;-)
По умолчанию в последних
версиях PHP включены обе опции. Как PHP поступает в этом случае? Кука
выставляется всегда. А ссылки автодополняются только если РНР не обнаружил cookies
с идентификатором сессии. Когда пользователь в првый раз за этот сеанс заходит
на сайт, ему ставится cookies, и дополняются ссылки. При следующем запросе, если
cookies поддерживаются, PHP видит cookies и перестает дополнять ссылки. Если cookies не
работают, то PHP продолжает исправно добавлять ид к ссылкам, и сессия не
теряется.
Пользователи, у которых работают cookies, увидят длинную ссылку с ID
только один раз.
С передачей идентификатора закончили. Теперь
осталось привязать к нему файл с данными на стороне сервера. PHP это сделает
за нас. Достаточно просто написать:
session_start();
$_SESSION['test']='Hello world!';
И PHP запишет в файл, связанный с этой
сессией, переменную test.
Здесь очень важное замечание.
Массив $_SESSION
-
особенный.
В нем, собственно, и находятся переменные, которые мы ходим
сделать доступными в различных скриптах.
Чтобы поместить переменную в сессию,
достаточно присвоить ее элементу массива $_SESSION.
Чтобы получить ее
значение - достаточно обратиться к тому же элементу. Пример будет чуть
ниже.
Cборкой мусора - удалением устаревших файлов PHP тоже занимается
сам. Как и кодированием данных и кучей всяких других нужных вещей. В результате
этой заботы работа с сессиями оказывается очень простой.
Вот мы, собственно,
и подошли к примеру работы сессий.
Пример очень маленький:
<?
session_start();
if (!isset($_SESSION['counter'])) $_SESSION['counter']=0;
echo "Вы обновили эту страницу ".$_SESSION['counter']++." раз. ";
echo "<br><a
href=".$_SERVER['PHP_SELF'].">обновить";
?>
Мы проверяем, есть ли у нас в сессии
переменная counter, если нет, то создаем ее со значением 0, а дальше выводим ее
значение и увеличиваем на единицу. Увеличенное значение запишется в сессию, и
при следующем вызове скрипта переменная будет иметь значение 1, и так
далее. Все очень просто.
Для того, чтобы иметь доступ к переменным
сессии на любых страницах сайта, надо написать ТОЛЬКО ОДНУ(!) строчку в самом
начале КАЖДОГО файла, в котором нам нужны сессии:
session_start();
И далее
обращаться к элементам массива $_SESSION. Например, проверка авторизации будет
выглядеть примерно так:
session_start();
if ($_SESSION['authorized']<>1) {
header("Location: /auth.php");
exit;
}
Удаление переменных из
сессии. Если у вас register_globals=off
, то достаточно
написать
unset($_SESSION['var']);
Если же
нет, то тогда рядом с ней надо написать:
session_unregister('var');
Область применения
Очень важно понимать, для чего сессии
стоит использовать, а для чего - нет.
Во-первых, помните, что сессии
можно применять только тогда, когда они нужны самому пользователю, а не для
того, чтобы чинить ему препятствия. Ведь он в любой момент может избавиться от
идентификатора!
Скажем, при проверке на то, что заполняет форму человек, а не
скрипт, пользователь сам заинтересован в том, чтобы сессия работала - иначе он
не сможет отправить форму! А вот для ограничения количества запросов к скрипту
сессия уже не годится - злонамеренный скрипт просто не будет возвращать
идентификатор.
Во-вторых. Важно четко себе представлять тот факт, что
сессия - это сеанс работы с сайтом, так как его понимает человек. Пришел,
поработал, закрыл браузер - сессия завершилась. Как сеанс в кино. Хочешь
посмотреть еще один – покупай новый билет. Стартуй новый сеанс. Этому есть и
техническое объяснение. Гарантированно механизм сессий работает только именно до
закрытия браузера. Ведь у клиента могут не работать cookies, а в этом случае,
естественно, все дополненные идентификатором ссылки пропадут с его
закрытием.
Правда, сессия может пропасть и без закрытия браузера. В силу
ограничений, рассмотренных в этой статье, механизм сессий не
может определить тот момент, когда пользователь закрыл браузер. Для этого
используется таймаут – заранее определенное время, по истечении которого мы
считаем, что пользователь ушел с сайта. По умолчанию этот параметр равен 24
минутам.
Если вы хотите сохранять пользовательскую информацию на более
длительный срок, то используйте cookies и, если надо - базу данных на сервере. В
частности, именно так работают все популярные системы авторизации:
- по
факту идентификации пользователя стартует сессия и признак авторизованности
передается в ней.
- Если надо "запомнить" пользователя, то ему ставится cookies,
его идентифицирующая.
- При следующем заходе пользователя на сайт, для того,
чтобы авторизоваться, он должен либо ввести пароль, либо система сама его
опознает по поставленной ранее cookies, и стартует сессию. Новую сессию, а не
продолжая старую.
В-третьих, не стоит стартовать сессии без разбору,
каждому входящему на сайт. Это создаст совершенно лишнюю нагрузку. Не
используйте сессии по пустякам – к примеру, в счетчиках. То, что спайлог
называет сессиями, считается, конечно же, на основе статистики заходнов, а не с
помощью механизма сессий, аналогичного PHP.
К тому же, возьмем
поисковик, который индексирует ваш сайт. Если поисковый робот не поддерживает
cookies, то PHP по умолчанию будет поставлять к ссылкам PHPSESSID, что может не сильно понравится поисковику, который, по слухам, и
так-то динамические ссылки не жалует, а тут вообще при каждом заходе - новый
адрес!
Если сессии используются для ограничения доступа к закрытому разделу
сайта, то все просто поисковик и не должен его индексировать. Если же
приходится показывать одну и ту же страницу как авторизованным, так и не
авторизованным пользователям, то тут поможет такой трюк – стартовать сессию
только тем, кто ввел пароль, или тем, у кого уже стартовала сессия.
Для этого
в начало каждой страницы вместо просто session_start()
пишем:
if (isset($_REQUEST[session_name()])) session_start();
таким образом, Мы стартуем сессию только
тем, кто прислал идентификатор.
Соответственно, надо еще в первый раз
отправить его пользователю – в момент авторизации.
Если имя и проль верные –
пишем session_start()
!
Возможные проблемы и их устранение
Самыми
распространенными ошибками, которые выдает РНР при попытке работать с сессиями,
являются такие:
Две из них,
Warning: Cannot send
session cookie - headers already sent
Warning: Cannot send session cache
limiter - headers already sent
вызваны одной и той же причиной,
решение описано в этом факе здесь
Третья,
Warning: open(/tmp\sess_SID, O_RDWR) failed: No such file or
directory (2) in full_script_path on line number
ранее она
выглядела, как
Warning: Failed to write session data
(files). Please verify that the current setting of session.save_path is correct
(/tmp)
,
если перевести ее с английского, подробно объясняет
проблему: недоступен указанный в php.ini путь к каталогу, в который пишутся
файлы сессий. Эту ошибку исправить проще всего. Просто прописать каталог,
который существует, и доступен на запись, например,
session.save_path = c:\windows\temp
И не забыть
перезагрузить Apache после этого.
Как выясняется, сообразительность
людская не имеет пределов, и поэтому я вынужден пояснить:
сообщение о третьей
ошибке (невозможно найти каталог) НЕИЗБЕЖНО приведет к появлению первых двух,
поскольку сообщение об ошибке - это вывод в браузер и после него заголовками
пользоваться нельзя. Поэтому не спешите искать преждевременный вывод, а сначала
пропишите правильный путь!
Следующей по распространенности проблемой
при работе с сессиями является тяжелое наследие register_globals. НЕ давайте
переменным скрипта имена, совпадающие с индексами массива $_SESSION!
При
register_globals=on значения будут перезаписывать друг друга, и вы
запутаетесь.
Если не работает, но и никаких сообщений не выводится, то
добавьте в самое начало скрипта две строчки, отвечающие за вывод ВСЕХ ошибок на
экран - вполне возможно, что ошибки есть, но вы их просто не
видите.
ini_set('display_errors',1);
error_reporting(E_ALL);
или смотрите
ошибки в error_log. Вообще, тема отображения сообщений об ошибках выходит за
рамки данной статьи, поэтому просто убедитесь хотя бы, что вы можете их видеть.
Чуть продробнее о поиске ошибок можно прочитать в этом разделе.
Если вы уверены, что
ошибок нет, но приведенный пример не работает все равно, то, возможно, в PHP не
включена передача ид через урл, а cookies по каким-то причинам не
работают.
Смотрите, что у вас с cookies.
Вообще, если у вас "не
работают" сессии, то сначала попробуйте передать идентификатор сессии руками, то
есть, сделать ссылку и приписать к ней идентификатор:
<?
session_start();
if (!isset($_SESSION['counter'])) $_SESSION['counter']=0;
echo "Вы обновили эту страницу ".$_SESSION['counter']++." раз.<br>
<a
href=".$_SERVER['PHP_SELF'].'?'.session_name().'='.session_id().">обновить";
?>
При этом следует убедится, что не
включена директива session.use_only_cookies
, которая запрещает PHP
принимать идентификатор сессии, если он был передан через URL
Если
этот пример не заработает, то проблема либо в банальных опечатках (половина "проблем" с сессиями происходит от неправильно написанного имени
переменной), либо в слишком старой версии PHP: поддержка сессий появилась в
версии 4.0, а массив $_SESSION
- в 4.1 (До этого использовался $HTTP_SESSION_VARS
).
Если же заработает -
то проблема в cookies. Отслеживайте - что за cookies ставит сервер браузеру,
возвращает ли браузер ее. Искать очень полезно, просматривая просматривая обмен HTTP-заголовками
между браузером и сервером.
Объяснение принципа работы cookies выходит за
рамки этого и так уж слишком большого текста, но хотя бы убедитесь, что сервер
cookies с идентификатором посылает, а браузер - возвращает. И при этом
идентификаторы совпадают друг с другом =)
Установка cookies должна выглядеть,
как
Set-Cookie:
PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6;
или как
Set-Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6;
path=/
(если вы запрашиваете скрипт не из корневого
каталога)
Ответ сервера должен выглядеть, как
Cookie:
PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6
либо
Cookie: PHPSESSID=prlgdfbvlg5fbsbshch6hj0cq6;
b=b
если браузер возвращает другие cookies, кроме идентификатора
сессии.
Если пример отсюда работает, а ваш собственный код - нет, то
проблема, очевидно, не в сессиях, а в алгоритме. Ищите, где потеряли переменную,
по шагам переносите пример отсюда, отлаживайте свой скрипт.
Еще одна
проблема может возникнуть, если вы используете перенаправление через header или
навигацию с помощью JavaScript.
Дело в том, что РНР автоматически дописывает
идентификатор сессии только к ссылкам вида <a
href=>
, но не делает этого для header-ов, яваскрипта,
мета-тегов.
Поэтому надо добавлять идентификатор руками, например,
так:
header("Location: /script.php?".session_name().'='.session_id());
Так
же, весьма редкая, и совершенно непонятно, откуда появляющаяся, проблема бывает
в том, что настройка session.save_handler имеет значение, отличное от files.
Если это не так - исправляйте.
Дополнительная
информация:
- Кроме cookies, механизм сессий посылает еще и заголовки, запрещающие
кэширование страниц (тот самый cache limiter). Для html это правильно и
необходимо. Но вот когда вы пытаетесь скриптом, проверяющим авторизацию,
отдать файл, то интернет эксплорер отказывается его скачивать. Именно из-за
этого заголовка. Вызов
session_cache_limiter("private");
перед стартом сессии должен решить
проблему.
- Как это ни кажется странным, но в массиве
$_SESSION
нельзя использовать числовые
индексы - $_SESSION[1], $_SESSION['10']
- cессии работать не будут.
- Где-то между версиями 4.2 и 5.0 невозможно было установить
session.use_trans_sid
с помощью ini_set()
. Начиная с
5.0 уже можно снова.
- До версии 4.3.3 cookies PHP отправлял cookies только если при старте сессии в
запросе отсутстввал идентификатор. Теперь же cookies посылается при каждом вызове
session_start()
Полезные Ссылки:
http://www.php.net/manual/ru/ref.session.php - самая последняя
и свежая информация о поддержке сессий в PHP в официальной документации, плюс
многочисленные комментарии пользователей. Настоятельно рекомендуется к
прочтению.
http://phpclub.ru/manrus/f/ref.session.html - Устаревший перевод этой главы на русский, из документации в переводе Александра
Пирамидина.
http://phpclub.ru/detail/article/sessions
В данной статье автор
сначала достаточно доступно рассказывает о механизме сессий, но методы, которые он предлагает
к концу статьи неоднозначные.