PHP AJAX Опрос
Создайте AJAX-опрос на PHP: запись голосов и вывод результатов без перезагрузки страницы с fetch, JSON и MySQL.
Что такое AJAX-опрос?
AJAX-опрос — небольшой виджет голосования, позволяющий пользователю выбрать вариант ответа, отправить голос на сервер в фоновом режиме и увидеть обновлённые результаты — всё это без перезагрузки страницы. AJAX (Asynchronous JavaScript and XML) — техника обмена данными с сервером через JavaScript уже после загрузки страницы.
В этой главе мы создадим полноценный опрос: таблицу MySQL для хранения вариантов ответов и счётчиков голосов, PHP-скрипт, который записывает голос и возвращает текущий подсчёт в формате JSON, а также небольшой JavaScript-код, отправляющий голос и отображающий результаты.
Если вам нужно непрерывно обновлять данные по таймеру (живое табло, панель статусов), применяются те же принципы — такой подход называется поллингом, и мы рассмотрим его в конце главы.
Зачем использовать AJAX для опроса?
- Без перезагрузки страницы. Пользователь остаётся на странице; обновляется только область опроса. Это происходит мгновенно и сохраняет состояние остальной части страницы (позицию прокрутки, видео, введённые данные формы).
- Меньше передаваемых данных. Вместо повторной отправки всего HTML-документа сервер возвращает только счётчики голосов в виде небольшого JSON-пакета.
- Результаты в реальном времени. Поскольку ответ содержит данные, а не разметку, можно сразу перерисовать полосу результатов и даже обновлять её по таймеру, отражая голоса других пользователей.
Шаг 1 — Создание таблицы базы данных
Каждый вариант ответа — отдельная строка. Мы храним текст варианта и текущий счётчик голосов.
CREATE TABLE poll_options (
id INT AUTO_INCREMENT PRIMARY KEY,
option_text VARCHAR(100) NOT NULL,
votes INT NOT NULL DEFAULT 0
);
INSERT INTO poll_options (option_text) VALUES
('PHP'),
('JavaScript'),
('Python');Если создание таблиц для вас ново, смотрите Создание таблицы MySQL и Вставка данных.
Шаг 2 — Создание HTML-формы
Форма отображает варианты ответов в виде переключателей и резервирует контейнер для результатов.
<form id="pollForm">
<p>What is your favorite language?</p>
<label><input type="radio" name="option" value="1"> PHP</label>
<label><input type="radio" name="option" value="2"> JavaScript</label>
<label><input type="radio" name="option" value="3"> Python</label>
<button type="submit">Vote</button>
</form>
<div id="pollResults"></div>Значение value каждого переключателя — это id варианта из базы данных: именно это нам нужно отправить на сервер.
Шаг 3 — Написание PHP-обработчика
PHP-скрипт выполняет две задачи: записывает голос (если он отправлен) и возвращает текущие результаты в формате JSON. Всегда используйте подготовленные выражения, чтобы значение, пришедшее из браузера, никогда не могло быть интерпретировано как SQL.
<?php
header('Content-Type: application/json');
$dsn = "mysql:host=localhost;dbname=your_database;charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
];
try {
$pdo = new PDO($dsn, 'username', 'password', $options);
// If a vote was submitted, increment that option's counter.
if (isset($_POST['option'])) {
$optionId = (int) $_POST['option'];
$update = $pdo->prepare(
"UPDATE poll_options SET votes = votes + 1 WHERE id = :id"
);
$update->execute([':id' => $optionId]);
}
// Always return the current tally.
$rows = $pdo->query(
"SELECT id, option_text, votes FROM poll_options ORDER BY id"
)->fetchAll();
$total = array_sum(array_column($rows, 'votes'));
echo json_encode([
'status' => 'success',
'total' => $total,
'options' => $rows,
]);
} catch (PDOException $e) {
http_response_code(500);
echo json_encode(['status' => 'error', 'message' => 'Database error']);
}Приведение входящего id с помощью (int) и его привязка через :id гарантируют, что значение обрабатывается строго как число, а не как часть запроса. Подробнее читайте в PHP Подготовленные выражения и json_encode().
Шаг 4 — Отправка голоса через JavaScript
Мы перехватываем событие submit формы, отправляем выбранный вариант через fetch(), затем отображаем полосу результатов на основе JSON-ответа.
const form = document.getElementById('pollForm');
const resultsBox = document.getElementById('pollResults');
form.addEventListener('submit', function (event) {
event.preventDefault(); // Stop the normal page reload.
const selected = form.querySelector('input[name="option"]:checked');
if (!selected) return; // Nothing chosen yet.
const body = new URLSearchParams({ option: selected.value });
fetch('poll.php', { method: 'POST', body })
.then(response => response.json())
.then(showResults)
.catch(error => console.error('Vote failed:', error));
});
function showResults(data) {
if (data.status !== 'success') {
resultsBox.textContent = 'Could not load results.';
return;
}
resultsBox.innerHTML = data.options.map(opt => {
const percent = data.total
? Math.round((opt.votes / data.total) * 100)
: 0;
return `<div>${opt.option_text}: ${opt.votes} votes (${percent}%)</div>`;
}).join('');
}event.preventDefault() останавливает полную отправку формы браузером — именно в этом и смысл AJAX. Процентное соотношение вычисляется на клиенте из счётчиков, возвращённых сервером, поэтому полоса всегда соответствует актуальным данным.
Шаг 5 — Поддержание актуальности результатов с помощью поллинга
До сих пор результаты обновлялись только когда голосовал текущий пользователь. Чтобы отражать голоса других пользователей, опрашивайте сервер по таймеру — запрашивайте последний подсчёт каждые несколько секунд и перерисовывайте:
function refreshResults() {
fetch('poll.php') // GET, no vote — just read the tally.
.then(response => response.json())
.then(showResults)
.catch(error => console.error('Refresh failed:', error));
}
setInterval(refreshResults, 5000); // Every 5 seconds.Поскольку PHP-обработчик возвращает подсчёт при каждом запросе (голос или нет), для получения свежих данных достаточно обычного GET-запроса. Выбирайте интервал аккуратно: короткий интервал создаёт ощущение большей живости, но увеличивает количество запросов. Для данных, которые должны быть по-настоящему в реальном времени, WebSockets или Server-Sent Events подходят лучше, чем поллинг с фиксированным интервалом.
Типичные ошибки
- Забыть
event.preventDefault(). Без него форма перезагружает страницу, и AJAX-запрос отменяется на полпути. - Доверять id варианта. Всегда приводите/валидируйте его и используйте подготовленное выражение; никогда не конкатенируйте
$_POSTв SQL. - Двойной подсчёт. Этот базовый пример позволяет одному браузеру голосовать множество раз. В продакшене ограничьте голосование по сессии, cookie или IP.
- Слишком агрессивный поллинг. Интервал в одну секунду на загруженной странице может перегрузить сервер; обычно достаточно 3–10 секунд.
Итоги
AJAX-опрос сочетает в себе таблицу MySQL, PHP-эндпоинт, который записывает голос и возвращает подсчёт в формате JSON, и JavaScript, отправляющий данные через fetch() и отображающий результаты — без перезагрузки страницы. Добавьте поллинг через setInterval, чтобы цифры оставались актуальными для всех.
graph TD;
A(User Selects Option and Clicks Vote) --> B(JavaScript Sends POST with fetch);
B --> C(PHP Increments Vote with Prepared Statement);
C --> D(PHP Returns Tally as JSON);
D --> E(JavaScript Renders Results Bar);
F(Timer Every 5s) --> G(GET poll.php for Latest Tally);
G --> D;