Оновлення
Long polling
Long polling — режим, когда ваш PHP-скрипт держит открытое HTTP-соединение с Telegram и ждёт новые события до 30 секунд. В отличие от обычного getUpdates (возвращает ответ сразу, даже если пустой), здесь сервер удерживает запрос, пока не появятся обновления или не истечёт таймаут. Это экономит ресурсы: вместо сотен пустых запросов в секунду — один долгий.
Параметры getUpdates для long polling
Ключевой параметр — timeout (в секундах). Ставьте 20–30. При timeout=0 (по умолчанию) получаете short polling — тысячи пустых ответов. Параметр allowed_updates фильтрует типы событий на стороне Telegram, уменьшая трафик.
Используем функцию telegramApi(), которую мы сделали в главе Первый запрос: getMe. Там мы написали обёртку над cURL с проверкой HTTP-кода, json_last_error и поля ok — здесь просто вызываем её.
<?php
// long_polling.php — однократный запрос для понимания механики
require_once __DIR__ . '/src/TelegramClient.php'; // функция telegramApi()
$token = getenv('TELEGRAM_BOT_TOKEN');
if (!$token) {
die('TELEGRAM_BOT_TOKEN не задан в окружении\n');
}
$offset = 0; // первый запуск — берём всё
$updates = telegramApi($token, 'getUpdates', [
'timeout' => 25, // long polling: ждём до 25 сек
'offset' => $offset,
'allowed_updates' => ['message', 'callback_query'],
]);
// Смотрим, что пришло — в терминале увидите структуру массива
var_dump($updates);
Пример ответа API
Telegram вернёт JSON, который telegramApi() уже декодирует в ассоциативный массив. Структура result — массив объектов Update:
{
"ok": true,
"result": [
{
"update_id": 8473921,
"message": {
"message_id": 12,
"from": {
"id": 123456789,
"is_bot": false,
"first_name": "Ivan",
"username": "ivan_test"
},
"chat": {
"id": 123456789,
"first_name": "Ivan",
"username": "ivan_test",
"type": "private"
},
"date": 1704067200,
"text": "/start"
}
}
]
}
Что смотреть в ответе:
ok—trueзначит успех,false— ошибка (тогда смотритеerror_codeиdescription).result— массив обновлений. Пустой массив[]— нормально, просто заtimeoutсекунд ничего не пришло.update_id— уникальный номер события. Обязательно сохраняйтеmax(update_id) + 1как следующийoffset, иначе при перезапуске получите те же события снова.message— входящее сообщение. Поля:message_id,from(юзер),chat(куда пришло),date(Unix-время),text(текст). Другие типы:callback_query,inline_query,channel_postи т.д.
Для отладки удобно выводить красивый JSON в терминал:
<?php
// Вставьте после получения $updates
echo json_encode($updates, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Бесконечный цикл с обработкой ошибок и сохранением offset
В production скрипт должен работать вечно. Оберните вызов в try/catch, делайте экспоненциальную задержку при ошибках сети и сохраняйте offset в файл или БД. Обрабатывайте каждое обновление отдельно — ошибка в одном не должна убить остальные.
Используем функцию telegramApi() из главы Первый запрос: getMe.
<?php
// worker.php — запускается под supervisord/systemd
require_once __DIR__ . '/src/TelegramClient.php';
$token = getenv('TELEGRAM_BOT_TOKEN');
$offsetFile = __DIR__ . '/var/last_offset.txt';
$nextOffset = (int)(file_exists($offsetFile) ? file_get_contents($offsetFile) : 0);
$delay = 1;
while (true) {
try {
$updates = telegramApi($token, 'getUpdates', [
'timeout' => 25,
'offset' => $nextOffset,
'allowed_updates' => ['message', 'callback_query'],
]);
$delay = 1; // успех — сбрасываем backoff
foreach ($updates as $update) {
// Обрабатываем каждое обновление отдельно
try {
processUpdate($update, $token);
} catch (Throwable $e) {
error_log('processUpdate failed: ' . $e->getMessage());
}
// Сдвигаем offset
$nextOffset = max($nextOffset, $update['update_id'] + 1);
}
// Сохраняем offset после пачки
if ($updates) {
file_put_contents($offsetFile, (string)$nextOffset);
}
} catch (Throwable $e) {
error_log('getUpdates failed: ' . $e->getMessage());
sleep(min($delay, 30));
$delay *= 2; // экспоненциальный бэкофф: 1, 2, 4, 8... до 30 сек
}
}
function processUpdate(array $update, string $token): void {
// Для отладки: видеть каждое событие в логах
error_log('Update: ' . json_encode($update, JSON_UNESCAPED_UNICODE));
if (isset($update['message'])) {
$msg = $update['message'];
$chatId = $msg['chat']['id'];
$text = $msg['text'] ?? '';
// Эхо-ответ для теста
if ($text === '/start') {
telegramApi($token, 'sendMessage', [
'chat_id' => $chatId,
'text' => 'Бот работает в long polling!',
]);
}
}
}
Почему один процесс — один getUpdates
Bot API отдаёт каждый Update только одному вызывающему. Если запустите два скрипта с getUpdates параллельно, они будут делить события: одно придёт первому, следующее — второму. Часть обновлений потеряется для каждого воркера. Поэтому long polling подходит для одного воркера. Для горизонтального масштабирования переходите на webhook + очередь.
Где запускать воркер
- supervisord / systemd — стандарт для PHP-демона. Автозапуск, рестарт при падении, логи в journald.
- php -S или cron — не подходят: первый блокирует процесс, второй даёт задержку до минуты между запусками.
- screen / tmux — только для локальной разработки. В production потеряете процесс при перезагрузке сервера.
Long polling vs Webhook
- Long polling: не нужен HTTPS, простой запуск, легко дебажить (видите ответ в терминале). Минусы — один воркер, держит PHP-процесс, задержка до ~1 сек после таймаута.
- Webhook: масштабируется, работает за nginx/php-fpm, нет постоянного соединения. Требует валидный SSL и публичный IP/домен.
Long polling хорош для разработки, MVP, тестов и ботов с небольшой нагрузкой (десятки сообщений в минуту). Как только появляется второй воркер или SLA выше 99% — переезжайте на webhook. Пошаговый переход — на странице «С polling на webhook».
Частые ошибки новичков
- Потерянный offset. Сохраняйте его в файл или БД, а не только в памяти: при рестарте процесса придётся обработать ту же пачку.
- Зависший процесс. Без
try/catchисключение вprocessUpdateубивает цикл — ставьте обработку ошибок вокруг каждого Update. - Игнорирование allowed_updates. По умолчанию Telegram шлёт все типы (включая
channel_post,pollи др.), что увеличивает payload. Фильтруйте:['message', 'callback_query', 'inline_query']. - Слишком маленький timeout. Ставьте 20–30 секунд. При
timeout=0(по умолчанию) получаете обычный short polling — тысячи пустых запросов.
Дальше: Webhook — production-вариант с HTTPS endpoint и очередью.