Exposing administrative operations—such as configuring webhook endpoints or clearing update queues—via public web routes poses an unnecessary security risk. Exposing web routes requires complex authentication middleware to prevent unauthorized third parties from re-pointing your webhook to a malicious server or flooding your endpoints. Executing administrative tasks through the Yii2 console interface eliminates this attack surface, leverages command-line arguments, and integrates seamlessly into deployment pipelines and system schedulers (like crontab or systemd timers).
This guide demonstrates how to construct a dedicated Yii2 console controller to manage core Telegram Bot API operations: conducting API health checks (getMe), registration and unregistration of webhooks (setWebhook, deleteWebhook), and executing batch database log rotations for processed update logs. This workflow does not rely on third-party Telegram SDK dependencies, using instead raw cURL calls to ensure full control over timeout handling, HTTP status verification, and error parsing.
Architectural Responsibilities
The console controller handles administrative routines only:
- Direct communication with Telegram's HTTPS API endpoints using isolated transport logic.
- Ingestion of environment parameters (
BOT_TOKEN,WEBHOOK_URL,WEBHOOK_SECRET_TOKEN). - Safe deletion of expired log records in bounded database transactions to avoid lock contention on high-throughput tables.
It does not handle real-time inbound update parsing or HTTP request lifecycle management, which remain the responsibility of web controllers or asynchronous queue consumers.
Console Controller Implementation
Create the CLI command file at commands/TelegramController.php. This class inherits from yii\console\Controller and exposes public methods mapped to CLI subcommands.
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
use yii\console\ExitCode;
use yii\db\Query;
class TelegramController extends Controller
{
public string $token = '';
public string $webhookUrl = '';
public string $secretToken = '';
public function init(): void
{
parent::init();
// Retrieve credentials from environment or Yii params
$this->token = (string) (getenv('TELEGRAM_BOT_TOKEN') ?: Yii::$app->params['telegramBotToken'] ?? '');
$this->webhookUrl = (string) (getenv('TELEGRAM_WEBHOOK_URL') ?: Yii::$app->params['telegramWebhookUrl'] ?? '');
$this->secretToken = (string) (getenv('TELEGRAM_SECRET_TOKEN') ?: Yii::$app->params['telegramSecretToken'] ?? '');
}
/**
* Health-check: Calls getMe to verify bot token validity.
*/
public function actionHealthCheck(): int
{
$this->stdout("Checking Telegram Bot API Connectivity...\n");
$response = $this->sendApiRequest('getMe');
if (!$response['ok']) {
$this->stderr("ERROR: Failed to connect. Reason: " . ($response['description'] ?? 'Unknown error') . "\n");
return ExitCode::UNSPECIFIED_ERROR;
}
$bot = $response['result'];
$this->stdout(sprintf("SUCCESS: Connected as @%s (ID: %d)\n", $bot['username'], $bot['id']));
return ExitCode::OK;
}
/**
* Register the public HTTP webhook endpoint with Telegram API.
*/
public function actionSetWebhook(?string $url = null, bool $dropPending = false): int
{
$targetUrl = $url ?? $this->webhookUrl;
if (empty($targetUrl)) {
$this->stderr("ERROR: Webhook URL is missing. Provide it via argument or config.\n");
return ExitCode::DATAERR;
}
$payload = [
'url' => $targetUrl,
'drop_pending_updates' => $dropPending,
'allowed_updates' => ['message', 'callback_query', 'my_chat_member'],
];
if (!empty($this->secretToken)) {
$payload['secret_token'] = $this->secretToken;
}
$this->stdout("Registering Webhook URL: {$targetUrl}...\n");
$response = $this->sendApiRequest('setWebhook', $payload);
if (!$response['ok']) {
$this->stderr("ERROR: setWebhook failed: " . ($response['description'] ?? 'Unknown') . "\n");
return ExitCode::UNSPECIFIED_ERROR;
}
$this->stdout("SUCCESS: Webhook successfully registered.\n");
return ExitCode::OK;
}
/**
* Remove registered webhook from Telegram API.
*/
public function actionDeleteWebhook(bool $dropPending = false): int
{
$payload = [
'drop_pending_updates' => $dropPending,
];
$this->stdout("Deleting Webhook registration...\n");
$response = $this->sendApiRequest('deleteWebhook', $payload);
if (!$response['ok']) {
$this->stderr("ERROR: deleteWebhook failed: " . ($response['description'] ?? 'Unknown') . "\n");
return ExitCode::UNSPECIFIED_ERROR;
}
$this->stdout("SUCCESS: Webhook deleted successfully.\n");
return ExitCode::OK;
}
/**
* Purge update logs older than N days in small chunks.
*/
public function actionRotateLogs(int $days = 14, int $batchSize = 1000): int
{
if ($days < 1) {
$this->stderr("ERROR: Days must be an integer greater than 0.\n");
return ExitCode::DATAERR;
}
$cutoffDate = date('Y-m-d H:i:s', strtotime("-{$days} days"));
$this->stdout("Rotating updates logged before {$cutoffDate}...\n");
$db = Yii::$app->db;
$totalDeleted = 0;
do {
// Delete in batches to prevent long table locks
$deletedCount = $db->createCommand()
->delete('telegram_update_log', 'created_at < :cutoff', [':cutoff' => $cutoffDate])
->execute();
// Note: If using SQL databases without LIMIT support in DELETE queries,
// delete via subquery matching primary keys:
/*
$subQuery = (new Query())
->select('id')
->from('telegram_update_log')
->where(['<', 'created_at', $cutoffDate])
->limit($batchSize);
$deletedCount = $db->createCommand()
->delete('telegram_update_log', ['id' => $subQuery])
->execute();
*/
$totalDeleted += $deletedCount;
$this->stdout("Deleted chunk of {$deletedCount} logs...\n");
} while ($deletedCount >= $batchSize);
$this->stdout("Log rotation complete. Total records removed: {$totalDeleted}\n");
return ExitCode::OK;
}
/**
* Execute direct cURL request to Telegram Bot API with error handling.
*/
private function sendApiRequest(string $method, array $params = []): array
{
if (empty($this->token)) {
return [
'ok' => false,
'description' => 'Bot token is empty. Ensure TELEGRAM_BOT_TOKEN environment variable is set.',
];
}
$url = "https://api.telegram.org/bot{$this->token}/{$method}";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($params),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
]);
$rawResponse = curl_exec($ch);
$curlErrno = curl_errno($ch);
$curlError = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($curlErrno !== 0) {
return [
'ok' => false,
'description' => "cURL Error ({$curlErrno}): {$curlError}",
];
}
$decoded = json_decode((string) $rawResponse, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return [
'ok' => false,
'description' => 'Failed to parse JSON response. HTTP Code: ' . $httpCode,
];
}
return $decoded;
}
}
Running Commands via CLI
To execute these actions on a server, use the default Yii CLI bootstrap binary yii from the root of your application repository.
# Verify bot account connectivity
php yii telegram/health-check
# Register public webhook endpoint
php yii telegram/set-webhook "https://example.com/telegram/webhook"
# Register webhook and drop any backlog of pending updates
php yii telegram/set-webhook "https://example.com/telegram/webhook" 1
# Remove webhook registration
php yii telegram/delete-webhook
# Purge updates logged more than 30 days ago
php yii telegram/rotate-logs 30
Application Configuration
Ensure your configuration files pass credentials to the console environment safely without committing sensitive raw strings to your repository. Modify config/params.php or config/console.php to fetch configuration parameters at runtime.
// config/params.php
return [
'telegramBotToken' => getenv('TELEGRAM_BOT_TOKEN') ?: '',
'telegramWebhookUrl' => getenv('TELEGRAM_WEBHOOK_URL') ?: '',
'telegramSecretToken' => getenv('TELEGRAM_SECRET_TOKEN') ?: '',
];
Webhook Secret Tokens and Update Isolation
When setting a webhook using actionSetWebhook, supplying a secret_token string (1 to 256 characters using A-Z, a-z, 0-9, _, -) causes Telegram to attach an X-Telegram-Bot-Api-Secret-Token header to every incoming HTTP request sent to your webhook URL.
In your web endpoint controller (e.g., controllers/TelegramController.php), you must validate this header prior to reading request payload:
public function actionWebhook()
{
$receivedSecret = Yii::$app->request->getHeaders()->get('X-Telegram-Bot-Api-Secret-Token');
$expectedSecret = Yii::$app->params['telegramSecretToken'];
if (empty($expectedSecret) || !hash_equals($expectedSecret, (string) $receivedSecret)) {
Yii::$app->response->statusCode = 403;
return 'Unauthorized';
}
// Parse update payload...
}
Automated Log Rotation via System Scheduler
When processing updates, high-traffic bots save raw JSON input into database tables for auditing and asynchronous processing. Over time, tables like telegram_update_log accumulate millions of rows, leading to degraded indexing speed and high storage usage.
Executing log rotation inside a system cron prevents database bloat. Add the command to your server's crontab (crontab -e):
# Purge logs older than 14 days every night at 02:00 AM
0 2 * * * /usr/bin/php /var/www/html/yii telegram/rotate-logs 14 > /dev/null 2>&1
Using chunked batch deletes inside actionRotateLogs prevents long-running transaction locks on your database tables, allowing concurrent write queries from incoming webhooks to complete without timing out.
Building high-scale integrations often requires extending API management workflows to handle structured payloads, Mini App authentications, and automated deployment pipelines. If you need dedicated development support or custom architectural solutions for Telegram platforms, consult BotCreator — studio that ships Telegram bots / Mini Apps. For low-level details on payload structures, inspect the official documentation at
Top comments (0)