Переклад цього гайду зʼявиться незабаром. Поки що текст доступний російською — перемкніться на RU або читайте нижче. Відкрити російську версію

PHP і фреймворки

Laravel

Laravel позволяет интегрировать Telegram Bot API двумя способами: через встроенный HTTP-клиент (обёртку над Guzzle) или готовые пакеты вроде irazasyed/telegram-bot-sdk. Ниже — нативный вариант без лишних зависимостей, максимально близкий к подходу из статьи HTTP-клиент на PHP. Мы вынесем сетевую логику в сервис-класс, а контроллер оставим только для парсинга update и вызова бизнес-логики.

Сервис-класс для исходящих запросов

Создайте app/Services/TelegramBot.php. В конструкторе читаем токен из конфига (он берётся из getenv('TELEGRAM_BOT_TOKEN') через config/services.php). Метод call() — универсальная обёртка: делает POST к api.telegram.org/bot<token>/<method>, проверяет HTTP-код, json_last_error и поле ok в ответе. При ошибке выбрасывает RuntimeException с понятным сообщением.

<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\RequestException;

class TelegramBot
{
protected string $token;
protected string $baseUrl;

public function __construct(?string $token = null)
{
$this->token = $token ?? config('services.telegram.token');
$this->baseUrl = "https://api.telegram.org/bot{$this->token}";
}

/**
* Универсальный вызов метода Bot API.
* @throws \RuntimeException при сетевой ошибке или ok=false
*/
public function call(string $method, array $params = []): array
{
try {
$response = Http::timeout(15)
->retry(3, 100)
->post("{$this->baseUrl}/{$method}", $params)
->throw()
->json();
} catch (ConnectionException $e) {
throw new \RuntimeException("Telegram API connection failed: " . $e->getMessage());
} catch (RequestException $e) {
$body = $e->response?->json();
$desc = $body['description'] ?? $e->getMessage();
throw new \RuntimeException("Telegram API error: {$desc}");
}

if (empty($response['ok'])) {
throw new \RuntimeException("Telegram API returned ok=false: " . ($response['description'] ?? 'unknown'));
}

return $response['result'];
}

public function sendMessage(int|string $chatId, string $text, array $options = []): array
{
return $this->call('sendMessage', array_merge([
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'HTML',
], $options));
}

public function answerCallbackQuery(string $callbackQueryId, ?string $text = null, bool $showAlert = false): array
{
return $this->call('answerCallbackQuery', [
'callback_query_id' => $callbackQueryId,
'text' => $text,
'show_alert' => $showAlert,
]);
}
}

Пример ответа API и как его посмотреть

Метод sendMessage возвращает объект Message. В коде можно проверить результат так:

<?php

$result = $bot->sendMessage(123456789, "Тестовое сообщение");
// Для отладки в консоли (php artisan tinker или job):
var_dump($result);
// Или красиво:
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

Типичный JSON-ответ (поле result):

{
"message_id": 42,
"date": 1700000000,
"chat": {
"id": 123456789,
"type": "private",
"first_name": "Ivan"
},
"text": "Тестовое сообщение"
}

Что смотреть: message_id — нужен для последующего редактирования/удаления (методы editMessageText, deleteMessage); chat.id — подтверждает, что сообщение ушло именно туда, куда планировали; date — unix-время отправки по серверу Telegram.

Конфигурация токена

Токен не хардкодим. В config/services.php читаем из окружения:

// config/services.php
return [
// ...
'telegram' => [
'token' => env('TELEGRAM_BOT_TOKEN'),
],
];
# .env
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11

Проверить, что токен подхватился, можно в Tinker:

php artisan tinker
> config('services.telegram.token')

Контроллер для входящих обновлений (Webhook)

Создайте app/Http/Controllers/TelegramWebhookController.php. Laravel автоматически декодирует JSON-тело в массив $request->all(). В главе Long polling мы делали функцию processUpdate — здесь логика та же, только внутри контроллера. Важно: всегда возвращать 200 OK с {"ok":true} в течение 1–2 секунд, иначе Telegram будет повторять доставку.

<?php

namespace App\Http\Controllers;

use App\Services\TelegramBot;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;

class TelegramWebhookController extends Controller
{
public function __invoke(Request $request, TelegramBot $bot): JsonResponse
{
$update = $request->all();

// Защита от пустых/некорректных запросов
if (!isset($update['update_id'])) {
return response()->json(['ok' => true]);
}

// --- Сообщения ---
if (isset($update['message'])) {
$msg = $update['message'];
$chatId = $msg['chat']['id'];
$text = $msg['text'] ?? '';

// В главе "Команды /start, /help" мы делали parseCommand — используем тот же подход
if (str_starts_with($text, '/start')) {
$bot->sendMessage($chatId, "Привет! Я бот на Laravel.\nНапиши /help для списка команд.");
} elseif ($text === '/help') {
$bot->sendMessage($chatId, "Доступные команды: /start, /help");
}
}

// --- Callback queries (inline-кнопки) ---
if (isset($update['callback_query'])) {
$cq = $update['callback_query'];
// В главе "callback_query" мы разбирали answerCallbackQuery — здесь вызываем метод сервиса
$bot->answerCallbackQuery($cq['id'], "Нажата кнопка: {$cq['data']}");
}

return response()->json(['ok' => true]);
}
}

Маршрут и регистрация Webhook

В routes/web.php (или api.php) добавьте маршрут. Параметр {token} в URL — простая защита от случайных запросов; в контроллере его можно проверить против config('services.telegram.token').

use App\Http\Controllers\TelegramWebhookController;

Route::post('/telegram/webhook/{token}', TelegramWebhookController::class)
->where('token', '.*');

Установите webhook разово — через Artisan-команду (удобно для деплоя) или в Tinker. Команда использует наш сервис TelegramBot и метод setWebhook.

<?php

namespace App\Console\Commands;

use App\Services\TelegramBot;
use Illuminate\Console\Command;

class SetupTelegramWebhook extends Command
{
protected $signature = 'app:setup-telegram-webhook';
protected $description = 'Set Telegram webhook URL';

public function handle(TelegramBot $bot): int
{
$url = config('app.url') . '/telegram/webhook/' . config('services.telegram.token');
$result = $bot->call('setWebhook', [
'url' => $url,
'drop_pending_updates' => true, // чистим старые обновления при деплое
'allowed_updates' => ['message', 'callback_query'], // только нужные типы
]);

// Результат setWebhook — просто true при успехе
$this->info("Webhook set: " . ($result === true ? 'ok' : 'failed'));

// Проверить текущий вебхук можно так:
$info = $bot->call('getWebhookInfo');
$this->line('Current webhook: ' . json_encode($info, JSON_PRETTY_PRINT));

return self::SUCCESS;
}
}

Запуск: php artisan app:setup-telegram-webhook. В выводе увидите JSON от getWebhookInfo — проверьте поля url, pending_update_count, last_error_date/last_error_message.

Использование сервиса в приложении (Jobs, Listeners, Controllers)

Сервис автоматически резолвится через контейнер. Для отправки уведомлений из фоновых задач оформите Job с implements ShouldQueue — Laravel сам сделает ретраи при исключениях из TelegramBot::call().

<?php

namespace App\Jobs;

use App\Services\TelegramBot;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class NotifyUser implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

public function __construct(
protected int|string $chatId,
protected string $message
) {}

public function handle(TelegramBot $bot): void
{
$bot->sendMessage($this->chatId, $this->message);
}
}

// Диспатч из любого места:
// NotifyUser::dispatch(123456789, 'Ваш заказ #123 принят!');

Настройте ретраи в config/queue.php (retry_after) и запускайте воркера с флагом --tries=3. При сетевой ошибке или ok=false Job упадёт в failed_jobs после исчерпания попыток.

Отладка: как посмотреть, что пришло от Telegram

В разработке удобно логировать весь update. Добавьте в начало контроллера:

<?php

// ... внутри __invoke
\Log::channel('telegram')->info('Incoming update', [
'update_id' => $update['update_id'] ?? null,
'payload' => $update,
]);

В config/logging.php добавьте канал:

// config/logging.php
'channels' => [
// ...
'telegram' => [
'driver' => 'daily',
'path' => storage_path('logs/telegram.log'),
'level' => 'info',
'days' => 14,
],
],

Теперь tail -f storage/logs/telegram-*.log покажет структуру каждого update — удобно для понимания полей message, callback_query, inline_query и др.

Дальше: БД и состояния (FSM)