Глиф Учебники

Урок 8. Сессии и куки

Курс «PHP» · урок 8 из 10 · 5 минут чтения

Каждый запрос к сайту для PHP — новый: программа выполнилась, отдала страницу и всё забыла. Но сайту нужно помнить, что посетитель вошёл в кабинет, что лежит у него в корзине, какую тему оформления он выбрал. Для этого есть cookie (по-русски часто «куки») и сессии.

Браузер cookie сайта theme dark PHPSESSID 8f3a… посетитель может прочитать и изменить любую cookie Сервер данные сессий 1c7e… другой посетитель 8f3a… $_SESSION 'user' => 'admin', 'views' => 3, посетитель их не видит и не изменит номер сессии
Браузер хранит cookie сайта — например, theme и номер сессии PHPSESSID — и посетитель может их менять, а данные $_SESSION лежат на сервере, и PHP находит их по номеру.

Главное правило: всё, чему нужно доверять (кто вошёл, какая у него роль), хранят в сессии, а не в cookie.

Первая сессия: счётчик

Файл counter.php — откройте его через встроенный сервер и несколько раз обновите страницу:

PHP
<?php
session_start();
$_SESSION['views'] = ($_SESSION['views'] ?? 0) + 1;
?>
<p>Вы открыли эту страницу <?= $_SESSION['views'] ?> раз(а).</p>

session_start() открывает сессию: создаёт новую или находит существующую по cookie из браузера. После этого массив $_SESSION работает как обычный массив, только его содержимое сохраняется между запросами. В другом браузере или в окне инкогнито счётчик начнётся заново — там своя сессия.

Браузер PHP: counter.php запрос 1 — cookie ещё нет session_start() новая сессия, views = 1 ответ: «…1 раз(а)» и cookie PHPSESSID=8f3a… cookie сохранена запрос 2 — с cookie PHPSESSID=8f3a… session_start() та же сессия, views = 2 ответ: «…2 раз(а)»
При первом запросе session_start() создаёт сессию и отправляет cookie PHPSESSID, со вторым запросом браузер присылает её обратно, и PHP находит ту же сессию — счётчик становится 2.

session_start() вызывают в самом начале файла, до любого вывода, даже до пробела или пустой строки перед <?php. Сессия передаёт cookie в заголовках ответа, а заголовки уходят до первого символа страницы. Иначе появится предупреждение headers already sent — разбор в справке Cannot modify header information.

Вход по паролю

Пример из трёх файлов: форма входа, закрытая страница и выход. Пароль хранится не открытым текстом, а в виде хеша — что это и почему так нужно, сказано в справке Как хешировать пароль. Получите хеш своего пароля в терминале:

Bash
php -r "echo password_hash('придумайте-пароль', PASSWORD_DEFAULT), PHP_EOL;"

Вместо придумайте-пароль впишите свой пароль. Команда выведет строку, которая начинается с $2y$. Вставьте её в login.php вместо слов «вставьте сюда хеш» — обязательно в одинарных кавычках: в двойных PHP примет знаки $ за переменные.

Файл login.php:

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
<?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
<?php
session_start();
$_SESSION = [];
session_destroy();
header('Location: /login.php');
exit;

На что обратить внимание:

Location: /admin.php ссылка «Выйти» login.php password_verify верен session_regenerate_id(true) $_SESSION['user'] = $login; admin.php для вошедших: в сессии должен быть user logout.php $_SESSION = []; session_destroy(); сессия очищена не вошёл — Location: /login.php вышел — Location: /login.php
При верном пароле login.php даёт сессии новый номер, кладёт логин в $_SESSION и отправляет на admin.php; без user в сессии admin.php возвращает на вход, а logout.php очищает сессию.

Для настоящего сайта пользователей хранят в базе данных — это следующий урок.

Cookie ставит функция setcookie — тоже до любого вывода:

PHP
<?php
setcookie('theme', 'dark', [
    'expires' => time() + 60 * 60 * 24 * 30,
    'path' => '/',
    'httponly' => true,
    'samesite' => 'Lax',
]);

На сайте с https добавьте 'secure' => true — тогда cookie будет передаваться только по защищённому соединению.

Прочитать cookie — массив $_COOKIE. Новая cookie появится в нём только при следующем запросе: в текущем браузер её ещё не присылал.

PHP
<?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 секунд). Для кнопки «Запомнить меня» на долгий срок делают отдельный механизм с токеном в базе — это тема для более сложных уроков.

Частые ошибки

Коротко

Проверьте себя

1. Где хранятся данные $_SESSION?
2. Почему session_start() нужно вызывать до любого вывода?
3. Сайт поставил cookie через setcookie. Когда значение появится в $_COOKIE?
4. Где безопасно хранить признак «посетитель — администратор»?