PHP & frameworks
Yii2
Practical integration of the Telegram Bot API into Yii2: a wrapper component over TelegramClient, a Webhook controller with CSRF disabled, a secret token in the URL, console commands for diagnostics, and the yii2-queue for heavy tasks.
In Yii2, integration with the Telegram Bot API relies on two elements: an application component (for outgoing requests) and a controller (for incoming updates via Webhook). The approach follows the pattern from article HTTP-клиент на PHP — moving networking logic into a reusable class, while the controller only parses the update and invokes business logic.
Application Component: Wrapper Around TelegramClient
In chapter HTTP-клиент на PHP, we built the TelegramClient class with retries, timeouts, and logging. In Yii2, it is convenient to wrap it into a component to call Yii::$app->telegram->sendMessage([...]) from anywhere. Place the file in components/Telegram.php:
<?php
namespace app\components;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
/**
* Компонент-обёртка над TelegramClient.
* Токен читаем из env — так безопаснее и удобнее для CI/CD.
*/
final class Telegram extends Component
{
public string $token = '';
public int $timeout = 15;
public bool $debug = false;
private \App\TelegramClient $client;
public function init(): void
{
parent::init();
if (empty($this->token)) {
throw new InvalidConfigException('Telegram token is not configured. Set TELEGRAM_BOT_TOKEN in .env');
}
$this->client = new \App\TelegramClient(
token: $this->token,
timeout: $this->timeout,
debug: $this->debug
);
}
/** Отправка сообщения — самый частый метод */
public function sendMessage(array $params): array
{
return $this->client->call('sendMessage', $params);
}
/** Универсальный вызов любого метода Bot API */
public function call(string $method, array $params = []): array
{
return $this->client->call($method, $params);
}
/** Удобный хелпер для ответа на callback_query */
public function answerCallbackQuery(string $callbackQueryId, string $text = '', bool $showAlert = false): array
{
return $this->client->call('answerCallbackQuery', [
'callback_query_id' => $callbackQueryId,
'text' => $text,
'show_alert' => $showAlert,
]);
}
}
Note: the TelegramClient constructor accepts named arguments (PHP 8.0+). If you are using an older version, pass them positionally.
Configuration in config/web.php
Add the component to the components section. The token is injected from an environment variable so it doesn't get committed to the repository:
// config/web.php (basic) или common/config/main.php (advanced)
return [
'components' => [
// ...
'telegram' => [
'class' => 'app\components\Telegram',
'token' => getenv('TELEGRAM_BOT_TOKEN') ?: '',
'timeout' => 10,
'debug' => YII_DEBUG, // логируем запросы в dev-режиме
],
],
];
Verify that the component has loaded: in the console using php yii telegram/test (see below) or simply var_dump(Yii::$app->telegram->call('getMe')); in any action.
Webhook Controller: Receiving Updates
Telegram sends a POST request with a JSON body. In Yii2, you need to disable CSRF validation for this action since Telegram does not have your cookies. Create controllers/TelegramController.php:
<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use yii\web\Response;
use yii\helpers\Json;
class TelegramController extends Controller
{
// Telegram не знает ваш CSRF-токен
public $enableCsrfValidation = false;
public function actionWebhook(): array
{
Yii::$app->response->format = Response::FORMAT_JSON;
$rawBody = Yii::$app->request->getRawBody();
if (empty($rawBody)) {
Yii::warning('Empty webhook payload');
return ['ok' => false, 'error' => 'Empty payload'];
}
$update = Json::decode($rawBody);
if (json_last_error() !== JSON_ERROR_NONE) {
Yii::error('Invalid JSON in webhook: ' . json_last_error_msg());
return ['ok' => false, 'error' => 'Invalid JSON'];
}
// Логируем входящий update для отладки
if (YII_DEBUG) {
Yii::info('Telegram update: ' . Json::encode($update, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
}
// Делегируем обработку сервису/хендлеру
$result = $this->handleUpdate($update);
// Telegram ждёт 200 OK и любой JSON — мы возвращаем статус обработки
return ['ok' => true, 'processed' => $result];
}
private function handleUpdate(array $update): bool
{
if (!isset($update['message'])) {
return false; // не сообщение — игнорируем
}
$message = $update['message'];
$chatId = $message['chat']['id'];
$text = $message['text'] ?? '';
$userId = $message['from']['id'] ?? null;
// Пример: эхо-бот с командой /start
if ($text === '/start') {
$resp = Yii::$app->telegram->sendMessage([
'chat_id' => $chatId,
'text' => "Привет! Я бот на Yii2. Твой ID: {$userId}",
'parse_mode' => 'HTML',
]);
$this->logApiResponse('sendMessage', $resp);
return true;
}
// Эхо для любого текста
if ($text !== '') {
$resp = Yii::$app->telegram->sendMessage([
'chat_id' => $chatId,
'text' => "Вы написали: " . htmlspecialchars($text),
]);
$this->logApiResponse('sendMessage', $resp);
return true;
}
return false;
}
private function logApiResponse(string $method, array $response): void
{
if (!YII_DEBUG) return;
if (($response['ok'] ?? false) === true) {
Yii::info("$method OK: " . Json::encode($response['result'] ?? []));
} else {
Yii::error("$method FAILED: " . Json::encode($response));
}
}
}
What the Telegram API Response Looks Like
After each call to sendMessage (or any other method), you receive an array. In debug mode, look at:
{
"ok": true,
"result": {
"message_id": 142,
"from": {"id": 123456789, "is_bot": true, "first_name": "MyBot", "username": "mybot"},
"chat": {"id": 987654321, "first_name": "Ivan", "type": "private"},
"date": 1700000000,
"text": "Привет! Я бот на Yii2. Твой ID: 987654321"
}
}
- ok —
trueindicates success,falseindicates an API error (seedescription,error_code). - result — a
Messageobject (for sendMessage) or another type depending on the method. - On error:
{"ok":false,"error_code":400,"description":"Bad Request: chat not found"}.
In the code above, the logApiResponse method writes only result to the Yii log on success and the entire response on error — avoiding unnecessary log clutter.
Routing and Secret Token in URL
As described in Webhook, the webhook URL must be unguessable. In Yii2, this is configured via urlManager:
// config/web.php
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
// Секретная часть генерируется один раз и хранится в .env
'telegram-webhook-' . getenv('TELEGRAM_WEBHOOK_SECRET') => 'telegram/webhook',
],
],
],
Now the public URL is https://yourdomain.com/telegram-webhook-SECRET123. Set it via the Bot API:
curl -X POST "https://api.telegram.org/bot$TELEGRAM_BOT_TOKEN/setWebhook" \
-H "Content-Type: application/json" \
-d '{"url": "https://yourdomain.com/telegram-webhook-SECRET123", "secret_token": "WEBHOOK_SECRET_FROM_ENV"}'
The secret_token parameter (not to be confused with the URL path) provides an additional check: Telegram will send the X-Telegram-Bot-Api-Secret-Token header, which you can verify in the controller before processing.
Console Command for Quick Inspection
It is convenient to have a command to check your token and webhook status without a browser. Create commands/TelegramController.php (in console/controllers/ for basic or console/controllers/ for advanced):
<?php
namespace app\commands;
use Yii;
use yii\console\Controller;
use yii\helpers\Json;
class TelegramController extends Controller
{
public function actionTest(): int
{
$me = Yii::$app->telegram->call('getMe');
if (($me['ok'] ?? false) === true) {
$this->stdout("Bot OK: @" . ($me['result']['username'] ?? 'unknown') . "\n", Console::FG_GREEN);
return ExitCode::OK;
}
$this->stderr("getMe failed: " . Json::encode($me) . "\n", Console::FG_RED);
return ExitCode::UNSPECIFIED_ERROR;
}
public function actionWebhookInfo(): int
{
$info = Yii::$app->telegram->call('getWebhookInfo');
$this->stdout(Json::encode($info, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n");
return ExitCode::OK;
}
}
Run: php yii telegram/test and php yii telegram/webhook-info. The output of getWebhookInfo will show url, pending_update_count, and last_error_date/last_error_message — the key indicators of webhook health.
Queues for Heavy Tasks
If processing an update takes time (external APIs, report generation), do not perform it inside the webhook handler — Telegram expects a response within a few seconds. Use yii2-queue (redis, db, file drivers):
// В handleUpdate вместо прямой отправки:
Yii::$app->queue->push(new \app\jobs\ProcessTelegramUpdateJob([
'update' => $update,
]));
// Сразу возвращаем 200 OK Telegram
return ['ok' => true, 'queued' => true];
The ProcessTelegramUpdateJob job executes asynchronously, has access to Yii::$app->telegram, and can retry automatically according to queue settings.
Next: Laravel