Yii2 Console Command for Bot Maintenance: setWebhook, Webhook Deletion, getMe, and Log Rotation

When developing and operating Telegram bots on Yii2, the console interface (CLI) is often bypassed, shifting administration tasks to web panels or manual GET requests in the browser. However, deployment automation, production diagnostics, and maintaining database cleanliness require reliable console tooling.

In this article, we will write a full-fledged консольный контроллер for Yii2 that solves three critical tasks: webhook management (including generation and storage of secret_token), a quick health check of the connection to the Telegram API, and safe rotation of accumulated update logs without locking database tables.

Console Controller Architecture in Yii2

To interact with Telegram Bot API, we will use classic cURL. Using third-party SDKs is often redundant and complicates debugging. Our controller will inherit from yii\console\Controller, retrieve the token from a secure environment (a component or environment variables), and execute requests with validation of HTTP response codes and JSON structure.

The first step is to create the basic structure of the console controller and the method for sending requests to the API. We do not use the unreliable file_get_contents, as it cannot flexibly handle timeouts and returns false on any HTTP errors (e.g., 4xx/5xx), depriving us of diagnostic information from Telegram.

<?php

namespace app\commands;

use Yii;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\helpers\Console;

class TelegramController extends Controller
{
private ?string $token = null;

public function init()
{
parent::init();
// Загружаем токен из переменных окружения или конфигурации Yii
$this->token = getenv("TELEGRAM_BOT_TOKEN") ?: (Yii::$app->params["telegramBotToken"] ?? null);
if (!$this->token) {
$this->stderr("Ошибка: Токен Telegram бота не сконфигурирован.\n", Console::FG_RED);
Yii::$app->end(ExitCode::CONFIG);
}
}

/**
* Универсальный метод отправки запросов к Telegram Bot API через cURL
*/
protected function sendRequest(string $method, array $params = []): array
{
$url = "https://api.telegram.org/bot{$this->token}/{$method}";
$ch = curl_init();

curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 15,
CURLOPT_CONNECTTIMEOUT => 5,
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);

if ($response === false) {
throw new \RuntimeException("cURL Error: {$error}");
}

$data = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \RuntimeException("Ошибка декодирования JSON: " . json_last_error_msg());
}

if (($data["ok"] ?? false) !== true) {
$description = $data["description"] ?? "Unknown error";
$errorCode = $data["error_code"] ?? $httpCode;
throw new \RuntimeException("Telegram API Error [{$errorCode}]: {$description}");
}

return $data["result"] ?? [];
}
}

Implementing getMe, setWebhook, and deleteWebhook

Now let's add actions for webhook lifecycle management and diagnostics. The getMe method serves as an excellent health-check tool to verify the server's network connectivity with Telegram servers and token validity.

When registering a webhook via setWebhook, it is critically important to use the secret_token parameter. This header (X-Telegram-Bot-Api-Secret-Token) will arrive in every POST request from Telegram. With its help, your webhook controller will be able to filter out fake requests from attackers who know your handler's URL. We will generate this token on the fly and save it locally for validation.

    /**
* Проверка работоспособности токена и связи с API (getMe)
*/
public function actionGetMe()
{
$this->stdout("Вызов метода getMe...\n", Console::FG_YELLOW);
try {
$result = $this->sendRequest('getMe');
$this->stdout("Соединение установлено!\n", Console::FG_GREEN);
$this->stdout("Бот: @{$result['username']} (ID: {$result['id']})\n", Console::FG_CYAN);
return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка диагностики: " . $e->getMessage() . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

/**
* Установка Webhook с автоматической генерацией Secret Token
* @param string $url Полный HTTPS URL обработчика на вашем сервере
*/
public function actionSetWebhook(string $url)
{
if (!filter_var($url, FILTER_VALIDATE_URL) || !str_starts_with($url, 'https://')) {
$this->stderr("Ошибка: URL должен быть валидным и начинаться с https://\n", Console::FG_RED);
return ExitCode::DATAERR;
}

// Безопасная генерация секретного ключа
$secretToken = Yii::$app->security->generateRandomString(32);
$this->stdout("Установка вебхука на URL: {$url}...\n", Console::FG_YELLOW);

try {
$this->sendRequest('setWebhook', [
'url' => $url,
'secret_token' => $secretToken,
'allowed_updates' => ['message', 'callback_query', 'my_chat_member'],
]);

// Сохраняем секрет в файл (или кэш/БД) для валидатора вебхук-контроллера
$path = Yii::getAlias('@runtime/tg_webhook_secret.bin');
file_put_contents($path, $secretToken);

$this->stdout("Вебхук успешно настроен!\n", Console::FG_GREEN);
$this->stdout("Секретный токен записан в runtime/tg_webhook_secret.bin\n", Console::FG_CYAN);
return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка установки вебхука: " . $e->getMessage() . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

/**
* Удаление вебхука бота
* @param bool $dropPending Сбросить ли необработанные апдейты на стороне Telegram
*/
public function actionDeleteWebhook(bool $dropPending = false)
{
$this->stdout("Удаление вебхука...\n", Console::FG_YELLOW);
try {
$this->sendRequest('deleteWebhook', [
'drop_pending_updates' => $dropPending
]);
$this->stdout("Вебхук успешно удален.\n", Console::FG_GREEN);
return ExitCode::OK;
} catch (\Exception $e) {
$this->stderr("Ошибка при удалении вебхука: " . $e->getMessage() . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
}

Rotation of Incoming Update Logs

When logging incoming updates (Updates) to the database for debugging, the log table grows rapidly. A high-load bot can generate millions of records per week, which slows down the DBMS and consumes disk space.

To prevent this problem, we will implement a rotation action. Cleaning up old logs in large chunks (DELETE FROM ... WHERE created_at ) can lock the table for a long time. The solution is to delete records in small batches in a loop with short pauses, so as not to overload the DBMS transaction log and not to interfere with parallel inserts from the webhook.

    /**
* Ротация базы данных: удаление логов апдейтов старше N дней
* @param int $days Срок хранения логов в днях
*/
public function actionRotateLogs(int $days = 14)
{
$this->stdout("Запуск ротации логов обновлений (удаление старше {$days} дней)...\n", Console::FG_YELLOW);

$db = Yii::$app->db;
$tableName = '{{%telegram_update_log}}';

if ($db->getTableSchema($tableName) === null) {
$this->stderr("Ошибка: Таблица {$tableName} не существует в БД.\n", Console::FG_RED);
return ExitCode::DATAERR;
}

$cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
$totalDeleted = 0;
$batchSize = 500; // Оптимальный размер пачки для удаления без блокировок

try {
do {
// Удаляем порциями по первичному ключу или индексу даты
$deleted = $db->createCommand(
"DELETE FROM {$tableName} WHERE created_at

New articles on Telegram

We explain what to automate in your business and how it works in practice. No spam.