Урок 8. Сессии и куки
Каждый запрос к сайту для PHP — новый: программа выполнилась, отдала страницу и всё забыла. Но сайту нужно помнить, что посетитель вошёл в кабинет, что лежит у него в корзине, какую тему оформления он выбрал. Для этого есть cookie (по-русски часто «куки») и сессии.
- Cookie — небольшая запись, которую сайт просит браузер сохранить. Браузер отправляет её обратно с каждым запросом к этому сайту. Хранится у посетителя, и он может её прочитать и изменить.
- Сессия — данные, которые хранятся на сервере. Браузер получает только cookie с номером сессии (по умолчанию она называется
PHPSESSID), а по номеру PHP находит данные.
Главное правило: всё, чему нужно доверять (кто вошёл, какая у него роль), хранят в сессии, а не в cookie.
Первая сессия: счётчик
Файл counter.php — откройте его через встроенный сервер и несколько раз обновите страницу:
<?php
session_start();
$_SESSION['views'] = ($_SESSION['views'] ?? 0) + 1;
?>
<p>Вы открыли эту страницу <?= $_SESSION['views'] ?> раз(а).</p>session_start() открывает сессию: создаёт новую или находит существующую по cookie из браузера. После этого массив $_SESSION работает как обычный массив, только его содержимое сохраняется между запросами. В другом браузере или в окне инкогнито счётчик начнётся заново — там своя сессия.
session_start() вызывают в самом начале файла, до любого вывода, даже до пробела или пустой строки перед <?php. Сессия передаёт cookie в заголовках ответа, а заголовки уходят до первого символа страницы. Иначе появится предупреждение headers already sent — разбор в справке Cannot modify header information.
Вход по паролю
Пример из трёх файлов: форма входа, закрытая страница и выход. Пароль хранится не открытым текстом, а в виде хеша — что это и почему так нужно, сказано в справке Как хешировать пароль. Получите хеш своего пароля в терминале:
php -r "echo password_hash('придумайте-пароль', PASSWORD_DEFAULT), PHP_EOL;"Вместо придумайте-пароль впишите свой пароль. Команда выведет строку, которая начинается с $2y$. Вставьте её в login.php вместо слов «вставьте сюда хеш» — обязательно в одинарных кавычках: в двойных PHP примет знаки $ за переменные.
Файл login.php:
<?php
session_start();
const ADMIN_LOGIN = 'admin';
const ADMIN_HASH = 'вставьте сюда хеш';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$login = $_POST['login'] ?? '';
$password = $_POST['password'] ?? '';
if ($login === ADMIN_LOGIN && password_verify($password, ADMIN_HASH)) {
session_regenerate_id(true);
$_SESSION['user'] = $login;
header('Location: /admin.php');
exit;
}
$error = 'Неверный логин или пароль';
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Вход</title>
</head>
<body>
<?php if ($error !== ''): ?>
<p style="color: red"><?= htmlspecialchars($error) ?></p>
<?php endif; ?>
<form method="post">
<p><input name="login" placeholder="Логин"></p>
<p><input name="password" type="password" placeholder="Пароль"></p>
<button>Войти</button>
</form>
</body>
</html>Файл admin.php — страница только для вошедших:
<?php
session_start();
if (!isset($_SESSION['user'])) {
header('Location: /login.php');
exit;
}
?>
<p>Здравствуйте, <?= htmlspecialchars($_SESSION['user']) ?>! <a href="/logout.php">Выйти</a></p>Файл logout.php:
<?php
session_start();
$_SESSION = [];
session_destroy();
header('Location: /login.php');
exit;На что обратить внимание:
password_verifyсравнивает введённый пароль с хешем. Сам пароль нигде не хранится.session_regenerate_id(true)после входа выдаёт сессии новый номер. Это защита от подмены сессии: номер, который мог узнать злоумышленник до входа, становится бесполезным.- После
header('Location: …')обязательно стоитexit. Без него PHP продолжит выполнять файл, и закрытая страница целиком уйдёт тому, кто не вошёл, — браузер просто не покажет её из-за перенаправления. - Сообщение об ошибке одно — «Неверный логин или пароль». Не подсказывайте, что именно неверно: так сложнее подбирать логины.
Для настоящего сайта пользователей хранят в базе данных — это следующий урок.
Cookie
Cookie ставит функция setcookie — тоже до любого вывода:
<?php
setcookie('theme', 'dark', [
'expires' => time() + 60 * 60 * 24 * 30,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
]);expires— когда cookie пропадёт: здесь через 30 дней (time()— текущее время в секундах). Без срока cookie живёт до закрытия браузера.path—/значит «для всего сайта».httponly— cookie не видна JavaScript на странице, её не украдёт чужой скрипт.samesite—Laxне отправляет cookie с запросами POST с чужих сайтов.
На сайте с https добавьте 'secure' => true — тогда cookie будет передаваться только по защищённому соединению.
Прочитать cookie — массив $_COOKIE. Новая cookie появится в нём только при следующем запросе: в текущем браузер её ещё не присылал.
<?php
$theme = $_COOKIE['theme'] ?? 'light';
if (!in_array($theme, ['light', 'dark'], true)) {
$theme = 'light';
}Значение проверяется по списку допустимых: посетитель может записать в cookie что угодно. Удалить cookie — поставить её с прошедшим сроком: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']).
Никогда не храните в cookie признак доступа вроде is_admin=1 или имя вошедшего пользователя: их легко изменить в браузере, и любой войдёт администратором. Такие данные — только в сессии.
Сколько живёт сессия
По умолчанию cookie сессии живёт до закрытия браузера, а данные на сервере удаляются, если сессией не пользовались примерно 24 минуты (настройка session.gc_maxlifetime, 1440 секунд). Для кнопки «Запомнить меня» на долгий срок делают отдельный механизм с токеном в базе — это тема для более сложных уроков.
Частые ошибки
headers already sent— доsession_start(),setcookieилиheaderчто-то выведено: текст, пробел, пустая строка перед<?php.- Сайт «забывает» вход на другой странице — в её начале нет
session_start(). Без него данных сессии на этой странице нет. - Верный пароль не подходит — хеш вставлен в двойных кавычках, и PHP испортил его, приняв части после
$за переменные. Нужны одинарные кавычки. - После
header('Location: …')забытexit— закрытая страница целиком уходит тому, кто не вошёл. Браузер её не покажет из-за перенаправления, но получит, а программа вродеcurlпокажет.
Коротко
- Cookie хранится в браузере и может быть изменена посетителем; сессия хранится на сервере.
session_start()— в начале файла, до любого вывода; дальше данные в$_SESSION.- После входа —
session_regenerate_id(true), при выходе —$_SESSION = []иsession_destroy(). - Пароли проверяются через
password_verify, послеheader('Location: …')всегдаexit. - Cookie ставит
setcookieсhttponlyиsamesite; новое значение видно в$_COOKIEсо следующего запроса.