Розробка продуктового Telegram-бота на Yii2 вимагає архітектурного поділу відповідальності. Виклик API через разові виклики file_get_contents або розмазані по контролерах cURL-запити призводить до дублювання коду, відсутності єдиної точки логування та падіння застосунку при перевищенні лімітів Telegram Bot API (HTTP 429 Too Many Requests).
У цій статті ми побудуємо ізольований сервісний шар: створимо компонент TelegramClient, налаштуємо його впровадження через DI-контейнер Yii2, забезпечимо безпечне зберігання токенів та реалізуємо автоматичні ретраї при відповіді 429 Too Many Requests.
Винесення конфігурації та токенів у params-local.php
Токен Telegram-бот а — це чутливі дані. Його категорично не можна зберігати в репозиторії VCS (Git). У фреймворку Yii2 для конфігурування секретів під конкретне середовище застосовується файл config/params-local.php, який додано в .gitignore.
Створимо параметри конфігурації та зареєструємо наш сервіс у контейнері залежностей (Dependency Injection Container).
// config/params-local.php
<?php
return [
'telegram.botToken' => getenv('TELEGRAM_BOT_TOKEN') ?: '1234567890:ABCdefGHIjklMNOpqrsTUVwxyz',
'telegram.apiBaseUrl' => 'https://api.telegram.org/bot',
];
Далі налаштуємо DI-контейнер у конфігураційному файлі застосунку config/web.php (або config/main.php для advanced-шаблону), щоб сервіс TelegramClient впроваджувався автоматично з потрібними аргументами.
// config/web.php
$params = array_merge(
require __DIR__ . '/params.php',
require __DIR__ . '/params-local.php'
);
$config = [
'id' => 'app-bot',
'basePath' => dirname(__DIR__),
'container' => [
'definitions' => [
\app\components\TelegramClient::class => [
'class' => \app\components\TelegramClient::class,
'botToken' => $params['telegram.botToken'],
'baseUrl' => $params['telegram.apiBaseUrl'],
'timeout' => 10,
'maxRetries' => 3,
],
],
],
'params' => $params,
];
return $config;
Створення сервісного компонента TelegramClient з ретраями
Сервісний клас повинен відповідати за виконання HTTP-запитів до API Telegram через cURL, перевірку кодів відповіді, парсинг JSON, обробку помилок мережі та коректну реакцію на ліміти. Telegram відправляє HTTP status code 429 при перевищенні частоти запитів, повертаючи параметр parameters.retry_after у секундах.
Нижче представлена повна реалізація компонента TelegramClient із циклом повторних спроб при помилці 429 та структурованим логуванням через Yii::error() та Yii::warning().
<?php
namespace app\components;
use Yii;
use yii\base\Component;
use yii\base\InvalidArgumentException;
class TelegramClient extends Component
{
public string $botToken = '';
public string $baseUrl = 'https://api.telegram.org/bot';
public int $timeout = 10;
public int $maxRetries = 3;
public function init(): void
{
parent::init();
if (empty($this->botToken)) {
throw new InvalidArgumentException('Параметр botToken не может быть пустым.');
}
}
/**
* Выполнение запроса к Telegram Bot API
*
* @param string $method Имя метода (например, sendMessage, sendPhoto)
* @param array $payload Массив параметров метода
* @return array
* @throws \RuntimeException
*/
public function sendRequest(string $method, array $payload = []): array
{
$url = rtrim($this->baseUrl, '/') . $this->botToken . '/' . $method;
$attempt = 0;
while ($attempt < $this->maxRetries) {
$attempt++;
$ch = curl_init();
$jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE);
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $jsonPayload,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CONNECTTIMEOUT => 5,
]);
$rawResponse = curl_exec($ch);
$curlErrno = curl_errno($ch);
$curlError = curl_error($ch);
$httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlErrno !== 0) {
Yii::error("cURL error [{$curlErrno}] при вызове {$method}: {$curlError}", 'telegram');
if ($attempt >= $this->maxRetries) {
throw new \RuntimeException("Ошибка сети Telegram API: {$curlError}");
}
sleep(1);
continue;
}
$response = json_decode($rawResponse, true);
$jsonError = json_last_error();
if ($jsonError !== JSON_ERROR_NONE) {
Yii::error("Некорректный JSON от Telegram API [{$method}]: {$rawResponse}", 'telegram');
throw new \RuntimeException("Некорректный формат ответа Telegram API.");
}
// Обработка лимита вызовов (429 Too Many Requests)
if ($httpCode === 429 || (isset($response['ok']) && $response['ok'] === false && ($response['error_code'] ?? 0) === 429)) {
$retryAfter = $response['parameters']['retry_after'] ?? 1;
Yii::warning("Telegram 429 Too Many Requests на методе {$method}. Попытка {$attempt}/{$this->maxRetries}. Пауза {$retryAfter} сек.", 'telegram');
if ($attempt < $this->maxRetries) {
sleep((int)$retryAfter);
continue;
}
}
// Обработка остальных ошибок API
if (!isset($response['ok']) || $response['ok'] !== true) {
$description = $response['description'] ?? 'Неизвестная ошибка';
$errorCode = $response['error_code'] ?? $httpCode;
Yii::error("Telegram API Error [{$errorCode}] на методе {$method}: {$description}. Payload: " . json_encode($payload, JSON_UNESCAPED_UNICODE), 'telegram');
return [
'ok' => false,
'error_code' => $errorCode,
'description' => $description,
];
}
return $response;
}
throw new \RuntimeException("Превышено максимальное число попыток запроса к Telegram API [{$method}].");
}
}
Використання TelegramClient у сервісах та контролерах
Завдяки інтеграції з DI-контейнером Yii2, компонент TelegramClient можна запитувати безпосередньо в конструкторах контролерів, консольних команд або доменних сервісів. Це робить код легко тестованим і незалежним від глобального стану.
Розглянемо приклад бізнес-логіки реєстрації заявки клієнта (ліда) з генерацією унікального ідентифікатора, збереженням у базу даних та сповіщенням адміністратора через наш TelegramClient.
<?php
namespace app\services;
use app\components\TelegramClient;
use Yii;
use yii\db\Connection;
class LeadProcessor
{
private TelegramClient $client;
private Connection $db;
public function __construct(TelegramClient $client, Connection $db)
{
$this->client = $client;
$this->db = $db;
}
/**
* Создание лида и отправка уведомления в Telegram
*/
public function createLead(int $chatId, string $customerName, string $phone): bool
{
// Генерация безопасного ID лида (не используем сессионные ключи)
$leadId = bin2hex(random_bytes(7));
$transaction = $this->db->beginTransaction();
try {
$this->db->createCommand()->insert('{{%leads}}', [
'lead_id' => $leadId,
'chat_id' => $chatId,
'name' => $customerName,
'phone' => $phone,
'created_at' => time(),
])->execute();
$transaction->commit();
} catch (\Throwable $e) {
$transaction->rollBack();
Yii::error("Ошибка сохранения лида {$leadId}: " . $e->getMessage(), 'leads');
return false;
}
$text = "<b>Новая заявка #" . htmlspecialchars($leadId) . "</b>
";
$text .= "Имя: " . htmlspecialchars($customerName) . "
";
$text .= "Телефон: " . htmlspecialchars($phone);
$response = $this->client->sendRequest('sendMessage', [
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'HTML',
'reply_markup' => [
'inline_keyboard' => [
[
['text' => 'Подтвердить', 'callback_data' => 'confirm_lead:' . $leadId],
]
]
]
]);
return isset($response['ok']) && $response['ok'] === true;
}
}
Ліміти Telegram Bot API та архітектурний чеклист
При побудові сервісного шару необхідно враховувати офіційні ліміти та особливості інфраструктури Telegram:
- Ліміти відправки: Не більше 30 повідомлень на секунду сумарно по всіх чатах і не більше 1 повідомлення на секунду в один конкретний чат/канал. При розсилках обов'язково надсилайте завдання в чергу (наприклад, через
yii2-queue) і використовуйте затримки (rate-limiting). - Довжина callback_data: Поле
callback_dataу inline-кнопка х обмежене 64 байтами. Не передавайте в ньому масив даних або тривалі токени — передавайте короткий ID (наприклад,bin2hex(random_bytes(7))) і запитуйте дані з БД. - Логування відповідей: Обов'язково налаштовуйте категорію логів
telegramуconfig/web.phpабоconfig/console.phpз окремим target-файлом (@runtime/logs/telegram.log), щоб помилки викликів API не губилися в загальному потоці. - Ідемпотентність вебхуків: Вхідний контролер вебхука повинен перевіряти заголовок
X-Telegram-Bot-Api-Secret-Tokenі фіксувати опрацьованіupdate_idу сховищі (Redis або БД) для виключення повторної обробки однієї події.
Створення спеціалізованого компонента TelegramClient із впровадженням залежностей та обробкою помилок робить ваш проєкт на Yii2 стійким до збоїв мережі та зміни лімітів з боку Telegram Bot API.
Якщо вам потрібна розробка збоєстійкої інфраструктури Telegram-бота під ключ з інтеграцією в CRM, команда BotCreator допоможе реалізувати рішення будь-якої складності.