Вызовите session_start() в начале страницы, а потом читайте и пишите $_SESSION['key'], как любой массив: PHP сохраняет его на сервере и находит снова при следующем запросе посетителя. Для небольшого значения, которое должен хранить браузер, вызовите setcookie('name', 'value', time() + 86400) и читайте его в последующих запросах из $_COOKIE['name'].
<?php
session_start(); // before any output
setcookie('theme', 'dark', time() + 60 * 60 * 24 * 30); // 30 days, also before output
$_SESSION['visits'] = ($_SESSION['visits'] ?? 0) + 1;
$theme = $_COOKIE['theme'] ?? 'light'; // set on an earlier visit
echo "You have opened this page {$_SESSION['visits']} times.";
Сессии и cookie имеют смысл только на протяжении нескольких HTTP-запросов из браузера, поэтому примеры, которым они нужны, записаны обычным кодом. Логика вокруг них это обычный PHP с массивами, и эта часть здесь выполняется: в каждом запускаемом блоке $session заменяет $_SESSION, а каждый вызов это один запрос.
Как работает сессия PHP
- Первый
session_start()создаёт случайный ID, отправляет его браузеру в cookie с именемPHPSESSIDи даёт вам пустой$_SESSION. - В конце запроса PHP сохраняет
$_SESSIONна сервере (по умолчанию в файлах, либо в Redis или базе данных). - При следующем запросе браузер отправляет cookie обратно,
session_start()читает ID и загружает сохранённый массив.
Браузер никогда не видит данные, только ID. Поэтому ID вошедшего пользователя место в $_SESSION, а не в вашей собственной cookie: посетитель может поменять свои cookie на любое значение, но не может поменять $_SESSION.
Вход и выход пользователя через сессию
Вход это проверить пароль, сменить ID сессии и сохранить ID пользователя. Выход это обратное действие, и в нём три шага.
<?php
// login.php
session_start();
$user = find_user_by_email($_POST['email'] ?? '');
if ($user && password_verify($_POST['password'] ?? '', $user['password_hash'])) {
session_regenerate_id(true); // new id after login: stops session fixation
$_SESSION['user_id'] = $user['id'];
header('Location: /dashboard.php');
exit;
}
$error = 'Wrong email or password.';
<?php
// any protected page
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: /login.php');
exit;
}
<?php
// logout.php
session_start();
$_SESSION = []; // 1. forget the data
$p = session_get_cookie_params(); // 2. delete the cookie
setcookie(session_name(), '', time() - 3600, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
session_destroy(); // 3. delete the stored session
header('Location: /');
exit;
session_regenerate_id(true) важнее, чем кажется. Если атакующий может заставить жертву использовать известный ему ID сессии (например, через ссылку), а ID после входа не меняется, атакующий тоже оказывается вошедшим. Новый ID при входе закрывает эту дыру. Сама проверка пароля описана на странице про password_hash.
Завершение сессии после бездействия
Собственная очистка PHP (session.gc_maxlifetime, по умолчанию 1440 секунд) запускается случайно и не является надёжным таймаутом. Храните время последнего запроса и проверяйте его сами. Блок использует фиксированные метки времени, чтобы был виден каждый случай; поменяйте $timeout или время.
Промежуток с 09:05 до 09:16:40 составляет 700 секунд, меньше 15-минутного лимита, поэтому пользователь остаётся в системе. Следующий промежуток 1000 секунд, поэтому сессия очищается.
Flash-сообщения
Flash-сообщение задаётся в одном запросе и показывается один раз в следующем, обычно "Сохранено." после редиректа. Храните его в сессии, а потом читайте и удаляйте за один шаг:
Установка cookie через setcookie
setcookie() принимает имя, значение и массив параметров (PHP 7.3 и новее). Задавайте эти четыре параметра у каждой cookie, если нет причины поступить иначе:
<?php
setcookie('theme', 'dark', [
'expires' => time() + 60 * 60 * 24 * 30, // 30 days; 0 means "until the browser closes"
'path' => '/', // send it on every page, not just this folder
'secure' => true, // only over HTTPS
'httponly' => true, // JavaScript cannot read it
'samesite' => 'Lax', // not sent on most cross-site requests
]);
$theme = $_COOKIE['theme'] ?? 'light';
На двух вещах часто спотыкаются:
$_COOKIEне меняется в том же запросе. Он содержит то, что браузер прислал с этим запросом. Cookie, которую вы устанавливаете сейчас, появится в$_COOKIEтолько в следующем.- Cookie это заголовки, поэтому
setcookie()должна выполняться до любого вывода, ровно какsession_start()иheader().
Чтобы удалить cookie, установите её снова с тем же именем и path и сроком действия в прошлом: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']).
Хранение массива в cookie
Значение cookie это строка не длиннее примерно 4 КБ, поэтому структурированные данные храните в JSON и считайте то, что вернулось, недоверенным: посетитель может это изменить. Никогда не делайте unserialize() для cookie: так можно создать объекты ваших собственных классов и запустить их магические методы, а json_decode() возвращает только массивы, строки, числа, логические значения, null или stdClass.
Флаги описаны на странице про json_encode. Для всего, что больше нескольких коротких значений, держите данные в сессии (или в базе данных), а в cookie храните только ключ.
Токены "запомнить меня"
Cookie "запомнить меня" не должна хранить ID пользователя или хеш пароля. Храните в cookie длинный случайный токен, а в базе данных только его хеш, и сравнивайте через hash_equals():
Значения меняются при каждом запуске, потому что random_bytes() случайна. Если база данных утечёт, хеши бесполезны без cookie, а hash_equals() сравнивает за постоянное время, поэтому проверка не выдаёт информацию через время выполнения.
Сессия или cookie: что использовать
Сессия ($_SESSION) | Cookie ($_COOKIE) | |
|---|---|---|
| Где хранится | На сервере | В браузере |
| Посетитель может прочитать или изменить | Нет (только ID) | Да |
| Размер | Ограничен вашим хранилищем | Около 4 КБ на cookie |
| Сколько живёт | До закрытия браузера или истечения сессии | До времени expires |
| Для чего | Вошедший пользователь, корзина, CSRF-токен, flash-сообщения | Тема, язык, выбор согласия, токен "запомнить меня" |
Частая ошибка: headers already sent
session_start(), setcookie() и header() отправляют HTTP-заголовки, а заголовки можно отправить только до тела ответа. Любой вывод раньше, даже пустая строка перед <?php в подключённом файле, приводит к:
Warning: session_start(): Session cannot be started after headers have already been sent in /var/www/page.php on line 3
Warning: Cannot modify header information - headers already sent by (output started at /var/www/page.php:2) in /var/www/page.php on line 4
Второе сообщение говорит, где начался вывод (page.php:2): перейдите к этой строке и уберите вывод или переместите session_start() выше. В файлах, где только PHP, отсутствие закрывающего ?> не даёт случайным пробелам после него стать выводом.
Часто задаваемые вопросы
Чем сессия отличается от cookie в PHP?
Cookie хранится в браузере посетителя и отправляется с каждым запросом, поэтому посетитель может её прочитать и изменить. Сессия хранит данные на сервере; браузер держит в cookie только случайный ID сессии (PHPSESSID). Всё, чему нужно доверять, например ID вошедшего пользователя, кладите в $_SESSION.
Как уничтожить сессию при выходе в PHP?
Очистите данные, удалите cookie сессии и уничтожьте сохранённую сессию: $_SESSION = [];, затем setcookie(session_name(), '', time() - 3600, '/'), затем session_destroy();. После этого сделайте редирект, чтобы следующая страница начиналась с чистого листа.
Как удалить cookie в PHP?
Установите её снова с тем же именем, путём и доменом и сроком действия в прошлом: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);. Сделайте ещё и unset($_COOKIE['theme']), если её читает остаток текущего скрипта, потому что $_COOKIE меняется только при следующем запросе.
Сколько живёт сессия PHP?
По умолчанию cookie сессии живёт до закрытия браузера (session.cookie_lifetime = 0), а сервер может удалить данные сессии через 1440 секунд (24 минуты) бездействия (session.gc_maxlifetime). Для фиксированного таймаута храните в сессии время последнего запроса и проверяйте его сами.
Почему session_start() пишет headers already sent?
ID сессии передаётся в заголовке cookie, а заголовки должны быть отправлены до любого вывода. echo, HTML или даже пустая строка или BOM перед <?php отправляют вывод первыми. Вызывайте session_start() в самом начале скрипта, до того как что-либо выведено.