PHP cессии

Введение

HTTP протокол сам по себе stateless.

Сессии нужны для хранения информации о пользователях. Они используют cookie делают примерно то же что и cookies но лишены большинства их недостатков

Сессии это инструмент для управления состоянием приложения.

Как и $_COOKIE $_SESSION это суперглобальная переменная ( массив ) , которая хранится на веб сервере.

У каждой сессии есть уникальный номер Session ID или SID

Сессия должна быть начата до отправки данных браузеру.

Данные на сервере и куки в браузере работают в связке. SID хранится в куки.

Сервер «помнит» пользователя пока работа с сайтом ведётся в сессии.

Сессия удаляется автоматически при закрытии браузера. рассмотрим эти задачи в следующем параграфе

Что можно сделать с помощью сессий

Пример

Из обычной жизни: когда вы получаете счёт за электричество, там содержится много разной информации. Но чтобы оплатить его, нужен только номер счёта. Вводите номер счёта и всё остальное подтянется с сервера.

Пример запроса от клиента

Request 1 GET /homepage.php HTTP/1.1 Host: eth1.ru Accept: */*

Рекомендуется отпрвлять Session ID с сервера в виде куки. Другой вариант: Session ID можно передать в URL параметре.

Response 1 HTTP/1.1 200 OK Content-type: text/html Set-Cookie: SID

Запрос к другой странице будет уже с SID

Request 2 GET /aboutus.php HTTP/1.1 Host: eth1.ru Cookie: SID Accept: */*

Примерно таким образом куки позволяют запоминать пользовательские настройки

Где настраивать сессии

Настроек более 40, способов их задавать тоже несколько.

Первое место, где можно задавать настройки сессий это файл php.ini .

С помощью php.ini можно изменить настройки для всего PHP сервера.

В Apache можно использовать файл httpd.conf для всего сервера и .htaccess для отдельных директорий.

Возможно, вы уже пользовались .htaccess для перенаправления запросов

Следующий способ задания настроек - функция ini_set(). Её нужно будет вызывать со всеми скриптами, где используются сессии, поэтому способ не очень эффективный.

Session Configurations Options

Полный список доступен на http://php.net

session.auto_start: автоматически начинает сессию.

По умолчанию выключена.

session.name: задаёт имя текущей сессии и сессионной куки

По умолчанию PHPSESSID.

Может быть изменено с помощью функции session_name()

session.save_path: путь по которому сохраняется информация о сессии

По умолчанию tmp директория сервера.

session.gc_maxlifetime: максимальное время жизни

По умолчанию 1440 секунд (24 минуты).

session.cookie_lifetime: время жизни куки, которая отправляется браузеру. По сути это значение, которое мы добавляем к time() когда задаём expires

По умолчанию 0.

session.cookie_path: задаёт cookie path

По умолчанию '/'.

session.cookie_secure: задаёт secure

Если включить то куки будут отправляться только по HTTPS.

По умолчанию выключена.

session.use_strict_mode: если включить то SID которые созданы не сервером будут отклонены.

По умолчанию выключена.

session.cookie_httponly: задаёт httponly

Если включить куки будет доступна только по HTTP (и HTTPS). То есть JavaScript или bbscript не смогут получить к куки доступ

По умолчанию выключена.

session.use_cookies: указывает нужно ли сохранять SID в cookies на стороне клиента.

По умолчанию включена.

session.use_only_cookies: заставляет сессию использовать только cookie для хранения SID. Работает совместно с session.use_cookies

По умолчанию включена.

session.use_trans_sid: контролирует использование «прозрачных» SID

Если включить - SID будет добавляться как параметр прямо в URL. Например:

https://www.eth1.ru/?SID

Эту опцию обычно включают только тогда, когда нет поддержки cookies

По умолчанию выключена.

Пользуйтесь trans sid с осторожностью так как это может поставить под угрозу безопасность пользователя.

Подробности на http://php.net/session.use-trans-sid

session.cache_limiter: указывает способ контроля за кэшем во время сессии.

Доступные варианты:

По умолчанию nocache.

Для сессий с аутентификацией нужно, чтобы кэширование в браузере было отключено.

Подробнее:

http://php.net

http://php.net

session.cookie_samesite: контролирует доступности куки в кроссдоменных запросах.

Доступные варианты: Lax и Strict

Кросдоменные запросы могу быть не только от злоумышленников. Их может делать ваш сайт с разных поддоменов. Либо вы можете следить за перемешением пользователя между своими проектами.

Функции

Полный список доступен на php.net

session_start(): начинает сессию и делает доступной переменную $_SESSION.

session_name(): переименовывает сессию.

Меняет значение, заданное с помощью функции session.name.

session_id(): получает или устанавливает текущий session id

set new session ID: session_id('<new SID>')

session_destroy(): удаляет всю информацию записанную в сессию

Пример .htaccess

Настройки вашего сервера могут отличаться, это просто пример

# Use php_flag for boolean values, # php_flag session.auto_start off php_flag session.use_cookies on php_flag session.use_only_cookies on php_flag session.use_trans_sid off php_flag session.use_strict_mode on # Use php_value for non-boolean values # Enclose values that contain punctuation in quotes. php_value session.cache_limiter nocache php_value session.cookie_samesite Lax php_value session.name AOSESSID php_value session.save_path ./tmp # Cookie settings php_value session.cookie_path '/' php_value session.cookie_lifetime 0 php_flag session.cookie_httponly on php_flag session.cookie_secure on

Инициализация сессии

Сессия начинается с помощью функции session_start()

Эта функция должна быть включена во все скрипты, в которых вам нужно использовать сессии.

Сперва выполняет проверку наличия активной сессии. Если сессии нет - начинает новую.

Сохраняет данные в супер глобальной переменной $_SESSION, которая является массивом

session_start(); $_SESSION['username'] = 'Andrei'; echo isset($_SESSION['username']) ? $_SESSION['username'] : 'Not available';

Если последняя строка вам непонятна - прочитайте статью

«Тернарный оператор в PHP»

Демонстрацию работы session_start() в вашем браузере вы можете сделать на этой странице

Посмотреть куки можно в Chrome DevTools → Application → Cookies

Выберите eth1.ru или eth1.ru и найдите AOSESSID

Изучить файл с данными о сессии можно в директории, которую вы указали в php_value session.save_path

В моём .htaccess указан

php_value session.save_path ./tmp

Поэтому я смотрю содержимое там

cat ./tmp/sess_d510566d6fc7dcbf9a9c21eb29d846ce

username|s:6:"Andrei";

Удаление сессии

Сессия истекает когда закрывается браузер, наступает таймаут, её явно делают просроченной.

Если не удалить данные, они так и будут лежать на сервере - это небезопасно.

Уничтожение сессии включает в себя:

session_destroy()

Удаляет все данные привязанные к сессии.

Не удаляет никаких переменных из суперглобальной переменной $_SESSION.

Не удаляет куки.

Возвращает boolean - удалились данные или нет

Если использовать только session_destroy() можно переиспользовать $_SESSION просто вызвав session_start()

unset()

unset() это стандартная PHP функция, которую использую не только с сессиями.

Чтобы очистить username нужно выполнить

unset($_SESSION['username']);

Чтобы очистить всё можно обойтись без unset()

$_SESSION = [];

Пример полного удалёния сессии

$_SESSION = []; $params = session_get_cookie_params(); $options = array('lifetime' => time() - 60); setcookie(session_name(), '', $options); session_destroy();

Хранение сессий в базе данных

По умолчанию PHP хранит каждую сессию в файле в session.save_path. Чтобы хранить сессии в базе данных, пишут класс, который реализует встроенный интерфейс SessionHandlerInterface, и передают его в session_set_save_handler(). Обычно такой класс объявлен в пространстве имён, и отсюда берётся первая ошибка.

Примеры ниже запущены на PHP 8.2 в образе php:8.2-cli Docker, текущая папка смонтирована как /app. База данных — SQLite в памяти, сервер не нужен.

Глобальные классы внутри пространства имён

Внутри пространства имён PHP ищет имя класса без ведущей обратной косой черты относительно этого пространства. PDO в этом файле превращается в App\Session\PDO:

db.php

<?php namespace App\Session; $db = new PDO('sqlite::memory:'); echo get_class($db), "\n";

php db.php

Fatal error: Uncaught Error: Class "App\Session\PDO" not found in /app/db.php:5 Stack trace: #0 {main} thrown in /app/db.php on line 5

Интерфейс — тоже глобальное имя. Класс-обработчик, который реализует его без импорта, нельзя даже объявить:

handler.php

<?php namespace App\Session; class DbSessionHandler implements SessionHandlerInterface { }

php handler.php

Fatal error: Uncaught Error: Interface "App\Session\SessionHandlerInterface" not found in /app/handler.php:5 Stack trace: #0 {main} thrown in /app/handler.php on line 5

То же самое относится к любому глобальному классу, интерфейсу и константе класса: PDOException, PDO::FETCH_ASSOC, DateTime. Импортируйте каждое имя один раз под строкой с namespace:

<?php namespace App\Session; use PDO; $db = new PDO('sqlite::memory:'); echo get_class($db), "\n";

php db.php

PDO

Имя с ведущей обратной косой чертой, new \PDO(...), тоже работает и выводит то же самое. Импорт удобнее, когда имя встречается в классе несколько раз.

Типы возвращаемых значений начиная с PHP 8.1

Как только интерфейс найден, PHP сверяет с ним каждый метод. Начиная с PHP 8.1 у методов SessionHandlerInterface есть типы возвращаемых значений, и обработчик, написанный для старого PHP, с методами вроде public function open($path, $name), при загрузке файла выдаёт по одному предупреждению на метод:

Deprecated: Return type of App\Session\DbSessionHandler::open($path, $name) should either be compatible with SessionHandlerInterface::open(string $path, string $name): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 14 Deprecated: Return type of App\Session\DbSessionHandler::close() should either be compatible with SessionHandlerInterface::close(): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 19 Deprecated: Return type of App\Session\DbSessionHandler::read($id) should either be compatible with SessionHandlerInterface::read(string $id): string|false, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 24 Deprecated: Return type of App\Session\DbSessionHandler::write($id, $data) should either be compatible with SessionHandlerInterface::write(string $id, string $data): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 31 Deprecated: Return type of App\Session\DbSessionHandler::destroy($id) should either be compatible with SessionHandlerInterface::destroy(string $id): bool, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 37 Deprecated: Return type of App\Session\DbSessionHandler::gc($max_lifetime) should either be compatible with SessionHandlerInterface::gc(int $max_lifetime): int|false, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice in /app/DbSessionHandler.php on line 42

Сервер, у которого в error_reporting нет E_DEPRECATED, эти сообщения не показывает, и обработчик кажется исправным до той версии PHP, в которой типы станут обязательными. Скопируйте сигнатуры из интерфейса вместо того, чтобы добавлять #[\ReturnTypeWillChange].

Рабочий обработчик

Класс с обоими исправлениями: глобальные имена импортированы, у каждого метода те же типы, что в интерфейсе. Столбец updated позволяет gc() удалять истёкшие сессии:

DbSessionHandler.php

<?php namespace App\Session; use PDO; use SessionHandlerInterface; class DbSessionHandler implements SessionHandlerInterface { public function __construct(private PDO $db) { } public function open(string $path, string $name): bool { return true; } public function close(): bool { return true; } public function read(string $id): string|false { $stmt = $this->db->prepare('SELECT data FROM sessions WHERE sid = ?'); $stmt->execute([$id]); return (string) $stmt->fetchColumn(); } public function write(string $id, string $data): bool { $stmt = $this->db->prepare('REPLACE INTO sessions (sid, data, updated) VALUES (?, ?, ?)'); return $stmt->execute([$id, $data, time()]); } public function destroy(string $id): bool { return $this->db->prepare('DELETE FROM sessions WHERE sid = ?')->execute([$id]); } public function gc(int $max_lifetime): int|false { $stmt = $this->db->prepare('DELETE FROM sessions WHERE updated < ?'); $stmt->execute([time() - $max_lifetime]); return $stmt->rowCount(); } }

Для неизвестного id сессии read() возвращает пустую строку, и PHP считает это новой пустой сессией. Тестовый скрипт создаёт таблицу, добавляет одну давно истёкшую сессию, запускает сессию через обработчик и вызывает gc():

test.php

<?php require __DIR__ . '/DbSessionHandler.php'; $db = new PDO('sqlite::memory:'); $db->exec('CREATE TABLE sessions (sid TEXT PRIMARY KEY, data TEXT, updated INTEGER)'); $db->exec("INSERT INTO sessions VALUES ('old1', 'username|s:3:\"Ann\";', 0)"); $handler = new App\Session\DbSessionHandler($db); session_set_save_handler($handler, true); session_id('demo1'); session_start(); $_SESSION['username'] = 'Andrei'; session_write_close(); foreach ($db->query('SELECT sid, data FROM sessions ORDER BY sid') as $row) { echo $row['sid'], ' ', $row['data'], "\n"; } echo 'gc removed: ', $handler->gc(1440), "\n"; echo 'left: ', $db->query('SELECT COUNT(*) FROM sessions')->fetchColumn(), "\n";

php test.php

demo1 username|s:6:"Andrei"; old1 username|s:3:"Ann"; gc removed: 1 left: 1

В строке сессии та же сериализованная строка, что и в файле сессии из главы про session_start(). Класс работает только через PDO, поэтому для другой базы данных нужны другая строка подключения и таблица sessions с типами столбцов этой базы.

Автор статьи: Андрей Олегович