Организация просмотра теста в обучающем модуле на PHP
Реализация просмотра теста на PHP
Самый эффективный способ: единая страница с PDO и обработкой POST
Как организовать просмотр теста с выборкой вопросов из базы и проверкой ответов на одной странице?
Рассмотрим пример, где все вопросы теста отображаются сразу, а проверка происходит после отправки формы. Такой подход минимизирует количество запросов к серверу и упрощает код.
Шаги:
- Создание таблицы в MySQL с вопросами, вариантами и правильными ответами.
- Подключение к базе через PDO.
- Получение всех вопросов и вывод их в форме.
- Обработка POST-запроса, подсчет баллов и вывод результата.
Пример кода (файл view_quiz.php):
<?php
// Подключение к БД
try {
$pdo = new PDO('mysql:host=localhost;dbname=quiz;charset=utf8', 'user', 'pass');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
die('Ошибка соединения: ' . $e->getMessage());
}
// Получение всех вопросов
$stmt = $pdo->query("SELECT * FROM questions");
$questions = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Обработка ответов
$result = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['quiz'])) {
$score = 0;
$total = count($questions);
foreach ($questions as $q) {
$correct = $q['correct'];
$userAnswer = isset($_POST['q' . $q['id']]) ? $_POST['q' . $q['id']] : '';
if ($userAnswer == $correct) {
$score++;
}
}
$result = "<div class='alert alert-info'>Вы набрали {$score} из {$total} баллов.</div>";
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Тест</title>
</head>
<body>
<h2>Пройти тест</h2>
<?= $result ?>
<form method="post">
<?php foreach ($questions as $q): ?>
<div class="question">
<p><span class="fw-bold">Вопрос:</span> <?= htmlspecialchars($q['question']) ?></p>
<label><input type="radio" name="q<?= $q['id'] ?>" value="1"> <?= htmlspecialchars($q['opt1']) ?></label><br>
<label><input type="radio" name="q<?= $q['id'] ?>" value="2"> <?= htmlspecialchars($q['opt2']) ?></label><br>
<label><input type="radio" name="q<?= $q['id'] ?>" value="3"> <?= htmlspecialchars($q['opt3']) ?></label><br>
</div>
<?php endforeach; ?>
<button type="submit" name="quiz">Отправить</button>
</form>
</body>
</html>
View quiz php (просмотр теста php)
Типичные проблемы и их решения:
- SQL-инъекции - в примере используется PDO и подготовленные запросы не нужны, но при динамической выборке всегда используйте подготовленные выражения.
- XSS-уязвимость - все данные из БД экранируются функцией htmlspecialchars().
- Потеря данных формы после отправки - после обработки ответов форма очищается. Для сохранения выбранных вариантов можно хранить их в сессии или передавать обратно.
Вариант 1: Использование сессии для пошагового прохождения
Как сделать пошаговый показ вопросов с сохранением прогресса?
Если тест длинный, пользователю удобнее видеть по одному вопросу на странице. Ответы сохраняются в сессии. Пример:
session_start();
// Инициализация номера вопроса
if (!isset($_SESSION['current'])) {
$_SESSION['current'] = 0;
$_SESSION['answers'] = [];
}
// Обработка ответа
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// сохранение ответа
$_SESSION['answers'][$_SESSION['current']] = $_POST['answer'];
$_SESSION['current']++;
// если вопросов больше нет - завершение
// ...
}
уроки php mysql (уроки по php и mysql)
Такой подход требует отслеживания состояния и управления навигацией.
Проблемы: Завершение сессии или истечение времени может привести к потере данных. Необходимо информировать пользователя о сроках действия.
Вариант 2: AJAX-загрузка вопросов без перезагрузки страницы
Как реализовать динамическую подгрузку вопросов через AJAX?
На стороне PHP готовим endpoint, который отдаёт один вопрос в формате JSON. Клиентский JavaScript подгружает следующий вопрос по мере ответа пользователя.
// get_question.php?id=<номер>
$id = intval($_GET['id']);
$stmt = $pdo->prepare("SELECT * FROM questions WHERE id = ?");
$stmt->execute([$id]);
$q = $stmt->fetch(PDO::FETCH_ASSOC);
echo json_encode($q);
На фронте используется fetch() для получения данных и рендеринга без перезагрузки.
Ошибки: Необходимо обрабатывать случай, когда вопрос не найден. Корректно настраивать заголовки CORS, если API на другом домене.
Вариант 3: Использование шаблонизатора (Twig) для отделения логики от представления
Как организовать чистый код с помощью шаблонизатора?
Установка Twig, создание шаблона quiz.html.twig, передача данных из контроллера.
// index.php
require_once 'vendor/autoload.php';
$loader = new \Twig\Loader\FilesystemLoader('templates');
$twig = new \Twig\Environment($loader);
echo $twig->render('quiz.html.twig', ['questions' => $questions, 'result' => $result]);
Шаблон содержит циклы и условия, что делает HTML чистым.
Типичная ошибка: Неправильные пути к шаблонам или отсутствие кэша. Для продакшена рекомендуется включить кэширование Twig.
Вариант 4: Защита от повторной отправки (CSRF-токен)
Как предотвратить повторную отправку формы теста?
Генерация уникального токена, хранение его в сессии, проверка при POST.
session_start();
if (empty($_SESSION['token'])) {
$_SESSION['token'] = bin2hex(random_bytes(32));
}
// В форме: <input type="hidden" name="token" value="<?= $_SESSION['token'] ?>">
// При обработке:
if (!hash_equals($_SESSION['token'], $_POST['token'])) {
die('Недействительный токен');
}
unset($_SESSION['token']); // однократное использование
Возможная проблема: Истечение сессии приводит к потере токена, и пользователь не может отправить форму. Рекомендуется отображать понятное сообщение.
Расширенные примеры с кодом и результатом
Пример 1: Полный тест с таймером и автоматической отправкой
Как реализовать обратный отсчёт времени и принудительную отправку?
PHP генерирует временную метку окончания, JavaScript отслеживает оставшееся время и отправляет форму при истечении.
// timer.php
session_start();
$timeLimit = 300; // 5 минут в секундах
if (!isset($_SESSION['end_time'])) {
$_SESSION['end_time'] = time() + $timeLimit;
}
$remaining = $_SESSION['end_time'] - time();
?>
<script>
var remaining = <?= $remaining ?>;
var timer = setInterval(function() {
remaining--;
document.getElementById('timer').innerText = remaining;
if (remaining <= 0) {
clearInterval(timer);
document.getElementById('quizForm').submit();
}
}, 1000);
</script>
<span id="timer"></span> секунд осталось
// Результат: на странице отображается таймер обратного отсчёта, по истечении форма отправляется автоматически.
Пример 2: Вывод результатов с детализацией по каждому вопросу
Как показать пользователю его ответы и правильные варианты после проверки?
Массив правильных ответов хранится в сессии после проверки, либо считывается из БД. При выводе добавляем стили для верных/неверных ответов.
// Часть обработчика
$userAnswers = $_POST;
$results = [];
foreach ($questions as $q) {
$correct = $q['correct'];
$userAnswer = isset($userAnswers['q' . $q['id']]) ? $userAnswers['q' . $q['id']] : '';
$isCorrect = ($userAnswer == $correct);
$results[] = [
'question' => $q['question'],
'user_answer' => $userAnswer,
'correct_answer' => $correct,
'is_correct' => $isCorrect
];
}
$_SESSION['results'] = $results;
// На странице результата можно вывести таблицу: номер вопроса, ответ пользователя, правильный ответ, иконка успеха/неудачи.
Пример 3: Импорт теста из JSON и динамический рендеринг
Как загрузить вопросы из внешнего файла JSON?
Используем file_get_contents и json_decode для чтения, затем рендерим аналогично.
$json = file_get_contents('questions.json');
$questions = json_decode($json, true);
// Далее такой же код, как с БД, только без PDO.
// Вопросы отобразятся корректно. JSON должен иметь структуру:
[{"id":1,"question":"...","opt1":"...","opt2":"...","correct":1}, ...]
Пример 4: Асинхронная проверка ответов один за другим
Как проверять каждый ответ сразу после выбора (без кнопки 'Отправить')?
Используется AJAX: onChange на radio-кнопках отправляет выбранный ответ на сервер, сервер возвращает правильность, и на странице отображается зелёный/красный индикатор.
// quiz.js
document.querySelectorAll('input[type="radio"]').forEach(input => {
input.addEventListener('change', function() {
const formData = new FormData();
formData.append('question_id', this.name.replace('q', ''));
formData.append('answer', this.value);
fetch('check_answer.php', { method: 'POST', body: formData })
.then(res => res.json())
.then(data => {
if (data.correct) {
this.parentElement.style.color = 'green';
} else {
this.parentElement.style.color = 'red';
}
});
});
});
// После выбора ответа цвет меняется мгновенно, пользователь сразу видит результат.
Пример 5: Использование готового PHP-фреймворка (Laravel) для тестов
Как упростить разработку с помощью Laravel?
Создание модели Question, контроллера QuizController, миграции и Blade-шаблонов. Пример маршрута и метода:
// routes/web.php
Route::get('/quiz', [QuizController::class, 'show']);
Route::post('/quiz', [QuizController::class, 'check']);
// QuizController.php
public function show() {
$questions = Question::all();
return view('quiz.show', compact('questions'));
}
// В Blade-шаблоне используется цикл @foreach и форма с CSRF. Результат возвращается с сессионным сообщением.