Работа с сессиями в PHP

Как уже известно, протокол HTTP позволяет веб-приложениям устанавливать "сессии" - диалог между клиентом и сервером, причём состояние этого диалога сохраняется от запроса к запросу.

Язык PHP, являясь в основном языком для веб-программирования,  предоставляет возможность пользоваться механизмом сессий HTTP, беря на себя большую часть забот при организации и хранении данных сессий. Напомню, что необходимо для работы механизма сессий HTTP:

  • Сервер должен сгенерировать уникальный номер сессии.
  • Номер сессии должен быть передан клиенту (обычно посредством Cookies).
  • Сервер должен уметь сохранять данные сессии в файлах или в базе данных, так чтобы их можно было восстановить, зная номер сессии, который клиент присылает серверу при последующих запросах (тоже посредством Cookies).

Собственно, единственное действие, которое необходимо предпринять в программе на PHP, чтобы шестерёнки механизма сессий закрутились - вызвать одну-единственную функцию: session_start(). Эта функция проделывает все необходимые действия:

  • Проверяет, не прислал ли клиент номер уже существующей сессии в Cookie или в параметрах запроса. Если клиент прислал номер сессии, то данные этой сессии загружаются из места постоянного хранения (например, файла) в память, и становятся доступны программе через массив $_SESSION. Если клиент не прислал номера сессии или такой сессии не существует на сервере - создаётся новая сессия с новым номером, а её данные также доступны через массив $_SESSION, который в случае новой сессии будет пустым. Номер вновь созданной сессии помещается в поле заголовка ответа сервера Set-Cookie.
  • Обеспечивает сохранение данных сессии. После того, как PHP-программа обработала запрос, текущее состояние массива $_SESSION сохраняется в месте постоянного хранения, чтобы вновь стать доступным при следующем запросе клиента.

Теперь, зная подробности, которые скрываются за session_start(), можем поиграть с этим механизмом. Пример ниже хранит в сессии одно число, которое увеличивается на единицу с каждым запросом клиента:

<?php
session_start();

$value = isset($_SESSION['value'])? $_SESSION['value']: 1;
$value += 1;
$_SESSION['value'] = $value;

echo $value;

При первом заходе на эту страничку, сервер пришлёт браузеру куки с номером сессии:

Set-Cookie: PHPSESSID=4ftvca7jmmnm04q95r3sdsk6r6; path=/

И этот же номер сессии браузер будет отправлять обратно серверу при каждом последующем запросе. Теперь, если в браузере очистить куки, на сервере будет создана новая сессия с другим номером, и счёт в нашем тестовом скрипте начнётся заново.

Более реальный пример: логин пользователя

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

<?php
// Сначала - служебные функции, которые нам понадобятся...

function findUser($login) {
  // Находим пользователя в списке.
  // Нормальный сайт сделал бы поиск в базе данных.
  $users = array(
    'admin' => array(
      'password' => '123',
      'title' => 'Администратор',
    ),
    'user' => array(
      'password' => 'qwe',
      'title' => 'Пользователь',
    ),
  );

  return isset($users[$login])? $users[$login]: null;
}

function getCurrentUser() {
  $user = isset($_SESSION['user'])? $_SESSION['user']: null;
  return $user;
}

function setCurrentUser($user) {
  $_SESSION['user'] = $user;
}
?>

<?php
session_start();
$message = null;

if(isset($_REQUEST['action'])) {
  // Обрабатываем действие пользователя
  $action = $_REQUEST['action'];
 
  if($action == 'login') {
    $user = findUser($_REQUEST['login']);
    if($user) {
      if($user['password'] == $_REQUEST['password']) {
        unset($user['password']); // На всякий случай, не будем хранить пароль в сессии.
        setCurrentUser($user);
      } else {
        $message = 'Указан неправильный пароль!';
      }
    } else {
      $message = 'Такой пользователь не зарегистрирован!';
    }

  } else  if($action == 'logout') {
    setCurrentUser(null);
  }
}

////////////// Далее идёт отображение веб-страницы

if($message) { // Выводим сообщение, если есть что сообщить
  ?>
  <div style="color:#fff; background:#AF0B00; padding:10px;"><?php echo $message ?></div>
  <?php
}

// Проверяем, залогинен ли пользователь.
// Если залогинен, показываем ему приветствие. Если нет - форму логина.
$user = getCurrentUser();
if($user) { ?>

  Привет, <?php echo $user['title'] ?>!<br/>

  <a href="?test=Test1">Ссылка1</a>
  <a href="?test=Test2">Ссылка2</a><br/>
  <?php echo isset($_GET['test'])? $_GET['test']: '' ?>
  <br/>

  <a href="?action=logout">Выйти</a>

<?php } else { ?>

  Вы еще не зашли на сайт. Введите логин и пароль:
  <form method="POST">
    <input type="hidden" name="action" value="login"/>
    Логин: <input type="text" name="login"/>
    Пароль: <input type="password" name="password"/>
    <input type="submit" value="Войти"/>
  </form>

<?php } ?>

Эта программка запрашивает логин и пароль, причём можно войти с логином user, qwe или admin, 123. Залогиненному пользователю показывается приветствие. При логине отображается сообщение, если имя пользователя или пароль указаны неверно.

Как только удалось зайти на этот "сайт", можно покликать по ссылкам (Ссылка1, Ссылка2), оставаясь при этом залогиненным пользователем.

Какие данные можно хранить в сессии?

По умолчанию PHP хранит данные сессии во временном файле в виде текста. В этом можно убедиться, заглянув в директорию с временными файлами PHP. Эта директория указана в phpinfo() в разделе Environment, TEMP. В этой директории вы найдёте файлы вида sess_4ftvca7jmmnm04q95r3sdsk6r6, где 4ftvca7jmmnm04q95r3sdsk6r6 - номер сессии, переданный в Cookie. Загляните в этот файл: если вы запустили самый первый пример выше, то в файле обнаружится примерно такое содержимое: "value|i:2;". Этот текст представляет собой сериализованное представление содержимого сессии, где хранится всего лишь одна переменная с числом.

Все значения, которые PHP-программа помещает в сессию через массив $_SESSION, при сохранении сессии превращаются в текстовый вид, а затем записываются в файл. Процесс преобразования значений переменных в текст называется "сериализацией". Таким образом, в сессию можно поместить любые данные, которые PHP способен сериализовать.

К счастью, в PHP сериализовать можно не только простые значения вроде чисел и строк, но так же и сложные структуры вроде массивов и объектов:

<?php
session_start();

echo '<pre>';
print_r($_SESSION);
echo '</pre>';

$_SESSION['testArray'] = array(
  1, 2, 3, 'one', 'two', 'three',
  'child' => array(5, 6, 7),
);

$obj = new stdClass();
$obj->x = 1234;
$obj->y = 4567;
$_SESSION['testObject'] = $obj;

Этот пример запишет в файл сессии такие данные:

testArray|a:7:{i:0;i:1;i:1;i:2;i:2;i:3;i:3;s:3:"one";i:4;s:3:"two";i:5;s:5:"three";s:5:"child";a:3:{i:0;i:5;i:1;i:6;i:2;i:7;}}testObject|O:8:"stdClass":2:{s:1:"x";i:1234;s:1:"y";i:4567;}

Хранение объектов в сессии

В сессии, как видно, можно хранить объекты. Но при этом следует помнить, что сохраняя в сессии объекты, ссылающиеся каким-либо образом на ваши "самодельные" классы, или объекты-экземпляры ваших классов, необходимо, чтобы объявление этих классов делалось до момента вызова session_start(). То есть, PHP должен знать класс до того, как встретит упоминание о нём при десериализации данных сессии.

При сохранении объектов бывают ситуации, когда стандартная сериализация объекта по каким-либо причинам неприемлема или вовсе невозможна. В таких случаях можно реализовать сериализацию вручную, объявив в классе "волшебные" методы __sleep() и __wakeup().

Кстати, сериализацию возможно осуществлять и "вручную", причём не обязательно для сохранения/загрузки данных сессии. Понадобиться это может, когда некоторые данные в приложении надо сохранить для использования позже, или передать по сети. Функции, которые могут пригодиться при сериализации/десериализации - serialize(), unserialize(), json_encode(), json_decode().
 

Что нельзя хранить в сессии?

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

Дополнительные возможности

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

Для ускорения процесса сохранения и загрузки данных сессии на высоко нагруженных сайтах может использоваться сервер Memcached, который хранит данные в памяти. Поддержка этого способа хранения сессий встроена в PHP и настраивается через файл конфигурации php.ini.

Можно задать имя параметра Сookie, через который передаётся номер сессии, указав его имя, время жизни, домен и другие параметры.

Эти и многие другие возможности настройки сессий HTTP в PHP доступны через функции session_*.

А также: 

Раздел: