morfeditorial/telegram-bot-bundle
morfeditorial/telegram-bot-bundle is a Symfony bundle for integrating Telegram bots into PHP applications. It provides configuration and services to connect your app with the Telegram Bot API and handle bot updates within a Symfony project.
Because telegram-bot-bundle is a Symfony bundle, a Laravel developer cannot rely on the usual php artisan vendor:publish / package-discovery conveniences. Treat it as a standalone OOP Telegram toolkit that you wire into Laravel manually.
Install and inspect the README first.
composer require morfeditorial/telegram-bot-bundle
The README is the only real source of truth right now — read the sections on Screens, Payload Routing (domain:action:args), and State Management before writing any code.
Confirm it boots outside Symfony. The v1.0.0 notes advertise DI autowiring and Symfony event dispatching "out of the box" — none of that exists in Laravel. Check composer.json for hard symfony/* requirements; you may pull in a large dependency tree even if you only use the Screen classes.
First use case — a single webhook endpoint. The fastest meaningful integration is a route that receives Telegram updates and hands them to the bundle's bot/kernel class:
// routes/api.php
Route::post('/telegram/webhook', TelegramWebhookController::class);
Bridge the transport layer yourself. The bundle's Long Polling / Webhook support is Symfony-flavoured. In Laravel you will typically:
Start with one Screen. Build a /start screen that replies with an inline keyboard, then iterate. Do not attempt stateful wizards until the basic dispatch loop works end-to-end.
Screen classes as the unit of work. Extend the bundle's Screen, implement supports() to declare which updates/payloads it claims, and handle() to act. Keep each screen small and single-purpose — the release notes explicitly position this as the replacement for legacy monolithic handlers.
final class StartScreen extends Screen
{
public function supports(Update $update): bool
{
return $update->message?->text === '/start';
}
public function handle(Update $update): void
{
$this->reply($update, 'Pick one:', $this->keyboard([
'orders:list' => 'My orders',
'profile:edit' => 'Edit profile',
]));
}
}
Payload routing (domain:action:args) as your callback contract. Stick to the colon-delimited format. Map it to a screen per domain, and branch on action inside handle() rather than creating a screen per button:
orders:list → OrdersScreenorders:show:42 → same screen, parse 42 from argsprofile:edit → ProfileScreenKeep args scalar and short (see Gotchas on the 64-byte limit).
State via Laravel's persistence layer. The bundle deliberately does not prescribe storage. Back it with cache()->store('redis'), a telegram_states table, or a dedicated Eloquent model keyed by chat_id:
Cache::put("tg:state:{$chatId}", ['screen' => 'checkout', 'step' => 2], now()->addHour());
Webhook controller pattern. Validate the Telegram secret token, resolve the bot from the container (bind it in an AppServiceProvider), dispatch the update through the bundle's router, and return 200 immediately:
public function __invoke(Request $request)
{
abort_unless($request->header('X-Telegram-Bot-Api-Secret-Token') === config('services.telegram.secret'), 403);
app(BotKernel::class)->handle(Update::fromArray($request->all()));
return response()->noContent();
}
Long polling as an Artisan command. Wrap the bundle's poller in a php artisan telegram:poll command. Use --once for tests and a supervisor for production. Do not run long polling inside a FPM request.
Event bridging. If the bundle dispatches Symfony events, register a small subscriber class (implements EventSubscriberInterface) that re-dispatches Laravel events, or register the bundle's dispatcher in your container pointing at Laravel's EventDispatcher. This keeps your listeners framework-native.
Config in config/services.php. Token, webhook secret, and allowed update types go here. There is no config/telegram-bot-bundle.php to publish.
ServiceProvider, no Facade, no auto-discovery, no publishable config. Expect to write an adapter layer (~50–100 lines) the first time.ContainerInterface, provide Laravel's container via a thin adapter.domain:action:args burns fast. Keep domains/actions to short slugs and use IDs rather than serialized payloads.setWebhook, and ensure your route is excluded from CSRF and rate limiting.supports(); on "no screen handled", dump your registered screen list — silent no-ops are the most common failure mode when supports() returns false everywhere.How can I help you explore Laravel packages today?