Интеграция сторонних HTTP-служб непосредственно в действия контроллера или обработчики очередей без уровня абстракции создает хрупкие, неуправляемые кодовые базы. При взаимодействии с API Telegram Bot в приложении Yii2 необработанные file_get_contents звонки или разрозненные фрагменты cURL вводят критические риски безопасности, неотслеживаемые исключения API, необработанные ограничения скорости (HTTP 429) и жестко закодированные учетные данные.
В этом уроке мы построим готовый к производству TelegramClient компонент для Yii2. Эта архитектура инкапсулирует стандартные сетевые запросы cURL, изолирует конфиденциальные маркеры внутри локальных параметров, напрямую интегрируется с контейнером Yii2 Dependency Injection (DI), автоматически обрабатывает бэк-оффы, ограничивающие скорость, и регистрирует сбои API через базовую инфраструктуру ведения журнала Yii2.
В этом руководстве основное внимание уделяется построению исходящего транспортного уровня HTTP для конечных точек API Telegram. Он не распространяется на миграцию базы данных, активные модели записей или настройку маршрута веб-перехватчика.
---
Хранение учетных данных в params-local.php
Жесткое кодирование учетных данных бота внутри компонентов приложения или истории фиксации ставит под угрозу безопасность и нарушает принципы изоляции среды. Yii2 предоставляет модель разделенной конфигурации: базовые значения находятся в config/params.php, в то время как переопределения, зависящие от среды, живут в config/params-local.php, которые должны быть исключены из контроля версий через .gitignore.
Сначала определите ключ-заполнитель в config/params.php:
<?php
return [
'adminEmail' => 'admin@example.com',
'telegramBotToken' => '',
];
Затем вставьте свой фактический токен бота, полученный от @BotFather в config/params-local.php:
<?php
return [
'telegramBotToken' => '1234567890:ABCdefGHIjklMNOpqrsTUVwxyZ123456789',
];
Это гарантирует, что среды разработки, подготовки и производства поддерживают отдельные экземпляры ботов без изменения основной кодовой базы.
---
Проектирование TelegramClient Компонент
Пользовательский компонент расширяется yii\base\Component для подключения к логике инициализации объекта Yii2. Он управляет ресурсами cURL, устанавливает таймауты выполнения, проверяет полезные нагрузки ответа и обрабатывает временные сбои API.
Когда Telegram возвращает код HTTP-статуса Слишком много запросов, ответ API включает объект JSON с parameters.retry_after указание, сколько секунд нужно подождать, прежде чем повторить попытку. Компонент считывает это свойство и выполняет ограниченный цикл повтора перед возвратом ошибки.
Создать components/TelegramClient.php:
<?php
namespace app\components;
use Yii;
use yii\base\Component;
use yii\base\InvalidConfigException;
use yii\base\Exception;
class TelegramClient extends Component
{
public string $botToken = '';
public int $timeout = 10;
public int $connectTimeout = 5;
public int $maxRetries = 3;
public function init(): void
{
parent::init();
if (empty($this->botToken)) {
throw new InvalidConfigException('The "botToken" property must be configured in TelegramClient.');
}
}
/**
* Executes a POST request to the Telegram Bot API.
*
* @param string $method API method (e.g., 'sendMessage', 'answerCallbackQuery')
* @param array $payload Key-value payload parameters
* @return array Decoded JSON response from Telegram
* @throws Exception If cURL encounters a network error or retries are exhausted
*/
public function sendRequest(string $method, array $payload = []): array
{
$url = sprintf('https://api.telegram.org/bot%s/%s', $this->botToken, $method);
$attempts = 0;
while ($attempts < $this->maxRetries) {
$attempts++;
$result = $this->executeCurl($url, $payload);
if ($result['status'] === 200 && isset($result['response']['ok']) && $result['response']['ok'] === true) {
return $result['response'];
}
// Check for Rate Limit (HTTP 429)
if ($result['status'] === 429 || (isset($result['response']['error_code']) && $result['response']['error_code'] === 429)) {
$retryAfter = $result['response']['parameters']['retry_after'] ?? 1;
Yii::warning(
sprintf('Telegram Rate Limit reached on %s. Attempt %d/%d. Waiting %d seconds.', $method, $attempts, $this->maxRetries, $retryAfter),
__METHOD__
);
if ($attempts < $this->maxRetries) {
sleep((int) $retryAfter);
continue;
}
}
// Log fatal error details if request failed completely
Yii::error(
sprintf(
"Telegram API Request Failed.
Method: %s
HTTP Status: %d
Payload: %s
Response: %s",
$method,
$result['status'],
json_encode($payload, JSON_UNESCAPED_UNICODE),
json_encode($result['response'], JSON_UNESCAPED_UNICODE)
),
__METHOD__
);
return $result['response'] ?? ['ok' => false, 'error_code' => $result['status'], 'description' => 'Unknown HTTP Error'];
}
throw new Exception(sprintf('Telegram API call "%s" failed after %d attempts.', $method, $this->maxRetries));
}
private function executeCurl(string $url, array $payload): array
{
$ch = curl_init();
$jsonPayload = json_encode($payload);
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $jsonPayload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'Content-Length: ' . strlen($jsonPayload),
],
CURLOPT_TIMEOUT => $this->timeout,
CURLOPT_CONNECTTIMEOUT => $this->connectTimeout,
CURLOPT_SSL_VERIFYPEER => true,
]);
$rawResponse = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErrno = curl_errno($ch);
$curlError = curl_error($ch);
curl_close($ch);
if ($curlErrno !== 0) {
Yii::error(sprintf('cURL execution error (%d): %s', $curlErrno, $curlError), __METHOD__);
return [
'status' => 0,
'response' => ['ok' => false, 'description' => 'cURL Error: ' . $curlError],
];
}
$decoded = json_decode((string) $rawResponse, true);
if (json_last_error() !== JSON_ERROR_NONE) {
Yii::error(sprintf('Failed to parse Telegram JSON response: %s', json_last_error_msg()), __METHOD__);
return [
'status' => $httpCode,
'response' => ['ok' => false, 'description' => 'Malformed JSON response'],
];
}
return [
'status' => $httpCode,
'response' => $decoded,
];
}
}
---
Регистрация службы с помощью Yii2 Dependency Injection
Чтобы избежать создания экземпляра класса вручную с помощью new TelegramClient(), настройте его внутри определений контейнера Yii2. Это разделяет классы, позволяет издеваться во время модульных тестов и централизует конфигурацию компонентов.
Открыть config/web.php и config/console.php если консольные работники взаимодействуют с Telegram) и зарегистрировать класс под контейнере клавиша:
$params = require __DIR__ . '/params.php';
if (file_exists(__DIR__ . '/params-local.php')) {
$params = array_merge($params, require __DIR__ . '/params-local.php');
}
$config = [
'id' => 'basic',
'basePath' => dirname(__DIR__),
'components' => [
// Standard components...
],
'container' => [
'singletons' => [
\app\components\TelegramClient::class => [
'class' => \app\components\TelegramClient::class,
'botToken' => $params['telegramBotToken'],
'timeout' => 12,
'maxRetries' => 3,
],
],
],
'params' => $params,
];
return $config;
Определяя TelegramClient внутри синглтоны, Yii2 создает экземпляр объекта один раз за жизненный цикл запроса по запросу через Constructor Injection или Yii::$container->get().
---
Использование TelegramClient в контроллерах и услугах
При настройке впрыска зависимостей вы можете впрыскивать TelegramClient непосредственно в контроллеры, консольные команды или обработчики заданий в очереди.
При визуализации пользовательского ввода внутри тела сообщения, отформатированного с помощью HTML (parse_mode = 'HTML'), всегда пропускайте значения через htmlspecialchars для предотвращения синтаксических ошибок, вызванных неэкранированным <, >или & символов.
Ниже приведен пример действия контроллера, обрабатывающего исходящие уведомления:
<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use yii\web\Response;
use app\components\TelegramClient;
class NotificationController extends Controller
{
private TelegramClient $telegram;
// Inject TelegramClient through Constructor Injection
public function __construct($id, $module, TelegramClient $telegram, $config = [])
{
$this->telegram = $telegram;
parent::__construct($id, $module, $config);
}
public function actionSendSystemAlert(string $chatId, string $userInputName): Response
{
// Sanitize untrusted input to avoid breaking Telegram HTML parser
$safeName = htmlspecialchars($userInputName, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$messageText = sprintf(
"<b>System Notification</b>
User <i>%s</i> triggered a critical alert.",
$safeName
);
$payload = [
'chat_id' => $chatId,
'text' => $messageText,
'parse_mode' => 'HTML',
'disable_web_page_preview' => true,
];
$response = $this->telegram->sendRequest('sendMessage', $payload);
if (isset($response['ok']) && $response['ok'] === true) {
return $this->asJson([
'success' => true,
'message_id' => $response['result']['message_id'],
]);
}
return $this->asJson([
'success' => false,
'error' => $response['description'] ?? 'Failed to deliver Telegram message.',
]);
}
}
---
Технические крайние случаи и эксплуатационные примечания
1. Запросы обратного вызова: При работе с интерактивными встроенными клавиатурами с помощью веб-перехватчиков звоните answerCallbackQuery немедленно очистить состояние загрузки на экране пользователя. Если процесс требует фоновых задач, вызовите answerCallbackQuery перед отправкой длительных заданий. 2. Ограничения по размеру полезной нагрузки 26 мая callback_data атрибут внутри встроенных кнопочных объектов не может превышать 64 байта. Не помещайте внутрь целые объекты базы данных или сложный JSON callback_data. Используйте короткие уникальные идентификаторы состояния, такие как шестнадцатерично закодированные случайные строки или первичные ключи базы данных, и разрешите состояние на своем бэкенде. 3. Разгрузка очереди для большого объема: В то время как внутренний контур обрабатывает переходные процессы Слишком много запросов коды состояния, синхронные веб-запросы будут блокировать работу вашего веб-сервера во время длительных перерывов. Для массовых уведомлений обрабатывайте сообщения асинхронно через yii2tech/queue или yiisoft/yii2-queue при поддержке Redis или RabbitMQ. 4. Сетевые таймауты: время ожидания соединения истекло (CURLOPT_CONNECTTIMEOUT) должны оставаться напряженными (например, 3–5 секунд), в то время как тайм-ауты выполнения (CURLOPT_TIMEOUT) должен быть установлен немного выше ожидаемой задержки, избегая зависания PHP-процессов во время сбоев в Telegram.
---
Нужна настраиваемая архитектура для сложных веб-перехватчиков, очередей обмена транзакционными сообщениями или синхронизации состояний мини-приложений? BotCreator создает готовые к производству интеграции Telegram, выделенные бот-клиенты и бэкенды для современных веб-приложений.