Сессии
HTTP – протокол без состояния, однако почти каждому приложению нужно сохранять состояние между запросами, например содержимое корзины покупок. Именно для этого служат сессии. Мы покажем:
- как использовать сессии
- как предотвратить конфликты имён
- как задать срок действия
При использовании сессий каждый пользователь получает уникальный идентификатор, называемый session ID, который передаётся в cookie. Он служит ключом к данным сессии. В отличие от cookie, которые хранятся на стороне браузера, данные сессии хранятся на стороне сервера.
Сессию мы настраиваем в конфигурации; особенно важен выбор времени истечения.
Работой с сессией занимается объект Nette\Http\Session, который вы получите,
попросив передать его через внедрение
зависимостей. В презентерах достаточно вызвать
$session = $this->getSession().
Запуск сессии
По умолчанию Nette запускает сессию автоматически в момент, когда мы
начинаем из неё читать или в неё писать данные. Запустить сессию
вручную можно через $session->start().
PHP при запуске сессии отправляет HTTP-заголовки, влияющие на кеширование (см. session_cache_limiter), и, возможно, cookie с session ID. Поэтому всегда нужно запускать сессию до отправки какого-либо вывода в браузер, иначе будет выброшено исключение. Так что если вы знаете, что при отрисовке страницы будет использоваться сессия, запустите её заранее вручную, например в презентере.
В режиме разработки сессию запускает Tracy, потому что использует её для отображения полос при перенаправлениях и AJAX-запросах в Tracy Bar.
Секции
В чистом PHP хранилище данных сессии реализовано как массив, доступный
через глобальную переменную $_SESSION. Проблема в том, что
приложения обычно состоят из множества независимых частей, и если всем
им доступен только один массив, рано или поздно возникнет
конфликт имён.
Nette Framework решает эту проблему разделением всего пространства на секции (объекты Nette\Http\SessionSection). Каждая часть затем использует собственную секцию с уникальным именем, и никакого конфликта возникнуть не может.
Секцию мы получаем из сессии:
$section = $session->getSection('уникальное имя');
В презентере достаточно использовать getSession() с параметром:
// $this - презентер
$section = $this->getSession('уникальное имя');
Существование секции можно проверить методом
$session->hasSection('уникальное имя'). Список имён всех существующих
секций возвращает $session->getSectionNames().
Работать с самой секцией затем очень легко с помощью методов
set(), get() и remove():
// запись переменной
$section->set('userName', 'john');
// чтение переменной, возвращает null, если её нет
echo $section->get('userName');
// удаление переменной
$section->remove('userName');
Чтобы получить все переменные из секции, можно использовать цикл
foreach:
foreach ($section as $key => $val) {
echo "$key = $val";
}
Как задать срок действия
Срок действия можно задать отдельным секциям и даже отдельным переменным. Мы можем дать входу пользователя истечь через 20 минут, но при этом продолжать помнить содержимое корзины покупок.
// срок действия секции истекает через 20 минут
$section->setExpiration('20 minutes');
Для задания срока действия отдельным переменным служит третий
параметр метода set():
// срок действия переменной 'flash' истекает через 30 секунд
$section->set('flash', $message, '30 seconds');
Помните, что время истечения всей сессии (см. конфигурацию сессии) должно быть равно времени, заданному отдельным секциям или переменным, или больше него.
Для отмены ранее заданного срока действия служит метод
removeExpiration(); чтобы сбросить срок действия конкретной переменной,
передайте её имя: removeExpiration('flash'). Чтобы немедленно удалить всю
секцию, используйте метод remove().
События $onStart, $onBeforeWrite
У объекта Nette\Http\Session есть события $onStart и $onBeforeWrite,
так что вы можете добавить callback'и, которые вызываются после запуска
сессии или перед её записью на диск и последующим завершением.
$session->onBeforeWrite[] = function () {
// записываем данные в сессию
$this->section->set('basket', $this->basket);
};
Работа с сессией
Обзор методов класса Nette\Http\Session для работы с сессией:
start(): void
Запускает сессию.
isStarted(): bool
Запущена ли сессия?
close(): void
Завершает сессию. Сессия завершается автоматически по окончании выполнения скрипта.
destroy(): void
Завершает и удаляет сессию.
exists(): bool
Содержит ли HTTP-запрос cookie с session ID?
regenerateId(): void
Порождает новый случайный session ID. Данные сохраняются.
getId(): string
Возвращает session ID.
Конфигурация
Сессию мы настраиваем в конфигурации. Если вы пишете приложение, которое не использует DI-контейнер, для настройки служат эти методы. Вызывать их нужно до запуска сессии.
setName(string $name): static
Задаёт имя cookie, в которой передаётся session ID. Стандартное имя –
PHPSESSID. Это полезно, если на одном сайте вы запускаете несколько
разных приложений.
getName(): string
Возвращает имя cookie, в которой передаётся session ID.
setOptions(array $options): static
Настраивает сессию. Можно задать все директивы сессии PHP (в формате
camelCase, то есть вместо session.save_path пишите savePath), а также readAndClose.
setExpiration(?string $expire): static
Задаёт время бездействия, после которого сессия истекает.
setCookieParameters(string $path, ?string $domain=null, ?bool $secure=null, SameSite|string|null $samesite=null): static
Задаёт параметры cookie. Значения параметров по умолчанию можно изменить в конфигурации.
setSavePath(string $path): static
Задаёт каталог, в котором хранятся файлы сессии.
setHandler(\SessionHandlerInterface $handler): static
Задаёт собственный обработчик, см. документацию PHP.
Безопасность прежде всего
Сервер исходит из того, что общается с одним и тем же пользователем, пока запросы сопровождаются одним и тем же session ID. Задача механизмов безопасности – обеспечить, чтобы это действительно было так и чтобы идентификатор нельзя было украсть или подменить.
Поэтому Nette Framework правильно настраивает директивы PHP так, чтобы session ID передавался только в cookie, был недоступен для JavaScript и чтобы любые идентификаторы в URL игнорировались. Более того, в критические моменты, например при входе пользователя, он порождает новый session ID.
Для настройки PHP используется функция ini_set, но, к
сожалению, некоторые хостинги её использование запрещают. Если это
случай вашего хостинга, попробуйте договориться, чтобы вам эту функцию
разрешили или хотя бы правильно настроили сервер.