Каталог можно просматривать, но пока нельзя добавить книгу через браузер. Сделаем форму: браузер отправит название, автора и год методом POST. PHP проверит формат, создаст объект и сохранит запись. Проверка будет на сервере — HTML-атрибуты помогают человеку, но не заменяют валидацию.

Форма для новой книги

Создайте шаблон templates/new-book.php. Маршрутизатор передаёт ему токен CSRF:

<form method="post" action="/books">
    <input type="hidden" name="csrf_token" value="<?php echo e($csrfToken); ?>">
    <label>Название <input name="title" required maxlength="200"></label>
    <label>Автор <input name="author" required maxlength="200"></label>
    <label>Год <input name="year" type="number" min="1" max="2100" required></label>
    <button type="submit">Добавить книгу</button>
</form>

required и maxlength помогают браузеру, но запрос можно отправить вручную. Сервер должен повторить все важные проверки.

Проверяем POST до сохранения

Запустите сессию до вывода HTML. Создайте токен один раз для сессии и добавьте его в форму:

session_start();
$_SESSION['csrf_token'] ??= bin2hex(random_bytes(32));
$csrfToken = $_SESSION['csrf_token'];

В обработчике POST сверяйте токен через hash_equals(), затем проверяйте ввод:

$errors = [];

if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') {
    $sentToken = $_POST['csrf_token'] ?? '';
    if (!is_string($sentToken) || !hash_equals($_SESSION['csrf_token'], $sentToken)) {
        http_response_code(403);
        exit('Форма устарела. Обновите страницу и попробуйте снова.');
    }

    $title = $_POST['title'] ?? '';
    $author = $_POST['author'] ?? '';
    $yearInput = $_POST['year'] ?? '';
    $year = is_string($yearInput) ? filter_var($yearInput, FILTER_VALIDATE_INT) : false;

    if (!is_string($title) || trim($title) === '') {
        $errors[] = 'Введите название книги.';
    }
    if (!is_string($author) || trim($author) === '') {
        $errors[] = 'Введите автора.';
    }
    if ($year === false || $year < 1 || $year > (int) date('Y')) {
        $errors[] = 'Проверьте год издания.';
    }
}

Значения $_POST могут оказаться не строками, например если запрос передал параметр несколько раз. После успешной проверки создайте Book и сохраните её.

Зачем нужен CSRF-токен

Браузер автоматически прикладывает cookies сессии к запросам. Злоумышленник может попытаться заставить открытый браузер отправить форму на сайт, где пользователь уже вошёл. Непредсказуемый токен, известный форме и серверной сессии, помогает проверить источник POST-запроса.

Токен не заменяет проверку прав и не защищает от XSS. Если появятся учётные записи, приложение должно отдельно проверять право пользователя менять конкретную книгу.

После сохранения перенаправьте на список

Сначала соберите ошибки. Если они есть, снова покажите форму. Если нет, создайте объект, сохраните его и перенаправьте браузер:

header('Location: /books', true, 303);
exit;

Статус 303 переводит следующий запрос на GET. Это Post/Redirect/Get: обновление страницы не отправит форму повторно.

Проверьте сценарии

  • Добавьте книгу с корректными данными.
  • Отправьте пустое название или текст вместо года: запись не сохраняется.
  • Измените токен вручную: сервер отклоняет запрос.
  • Обновите страницу после успешного добавления: дубля не появляется.

Теперь браузер может менять каталог. В следующей части заменим JSON-файл на SQLite через PDO и подготовленные запросы.

Часть 13 из 16.

← Часть 12: Маршрутизация и шаблоны PHP: собираем веб-страницу · Часть 14: PDO и SQLite в PHP: база и подготовленные запросы →

Документация