nutgram/symfony-bundle
A Symfony bundle that integrates Nutgram, the PHP Telegram bot framework, into Symfony applications. It provides bundle configuration, service container integration, and a Symfony-native setup for building, running, and maintaining Telegram bots.
This release (1.2.0) is a compatibility release: void return types added for PHP 8.4 deprecation compliance, and Symfony 8 support. There are no new features, so onboarding steps are unchanged from 1.1.x.
Reality check for Laravel developers: nutgram/symfony-bundle is a Symfony bundle, not a Laravel package. It ships a NutgramBundle, DI extension, webhook controller, and console commands wired through Symfony's kernel. Do not composer require it into a Laravel app expecting it to boot — there is no ServiceProvider, no config/nutgram.php publish tag, and its config tree is a Symfony Configuration node.
Two viable paths:
nutgram/nutgram directly (or nutgram/laravel, which wraps it) and read this bundle as a reference implementation for how Nutgram expects to be wired: handler registration, webhook route, and the cached bot container.First use case for a Symfony-side integration:
composer require nutgram/symfony-bundle:^1.2
// config/bundles.php
return [
// ...
Nutgram\SymfonyBundle\NutgramBundle::class => ['all' => true],
];
TELEGRAM_BOT_TOKEN=123456:ABC...
Then verify registration:
bin/console debug:container --tag=nutgram.handler
bin/console nutgram:run
First place to look: the bundle's DependencyInjection/Configuration.php and the nutgram.handler tag docs — that is where 90% of "my handler never fires" issues originate.
Handler registration via tags. The bundle's core value is autoconfiguration: classes implementing Nutgram\Handlers\Handler (or annotated with #[OnText], #[OnCommand], etc.) are collected by tag and registered into the bot container. This mirrors what you'd hand-roll in Laravel:
// Symfony — autoconfigured, zero boilerplate
#[OnCommand('start')]
class StartHandler
{
public function __invoke(Nutgram $bot): void
{
$bot->sendMessage('Hello');
}
}
// Laravel equivalent — you write the binding yourself
$this->app->singleton(Nutgram::class, fn () => new Nutgram(config('nutgram.token')));
Webhook vs. long polling. In Symfony, the bundle registers a webhook controller and route; switch modes in bundle config (nutgram: { hook: true }). In Laravel you'd instead register Route::post('/telegram/webhook', WebhookController::class) and call $bot->setWebhook(...) from a one-off artisan command. Keep the webhook path in sync with nutgram:hook:set / the Laravel equivalent, or you get silent 404s from Telegram.
Cached bot container. Both stacks benefit from compiling the handler map once and caching it (Symfony container cache; in Laravel, php artisan config:cache plus a BotContainer singleton bound in a provider). Registering handlers on every request is the single biggest latency cost in a Telegram bot.
Queue offloading. Telegram requires a webhook response within ~60s. Anything slow (DB writes, third-party calls) should be pushed to the queue. Symfony: Messenger. Laravel: Horizon/dispatch(). Pattern: handler parses the update, dispatches a job, returns immediately.
Environment/config layering. Token, webhook secret, and allowed-updates belong in env, not in committed config. In Symfony, %env(TELEGRAM_BOT_TOKEN)% bound in services.yaml; in Laravel, config/nutgram.php reading env().
Multi-bot. The bundle is effectively single-bot per kernel config. If you need several bots, run separate Symfony apps/kernels or fall back to nutgram/nutgram with per-bot containers — easier in Laravel where you can register named singletons.
Upgrading within 1.2.x: bump the constraint, run bin/console cache:clear, then smoke-test one webhook update. Nothing else to migrate from 1.1.1.
The void return types are a subclassing BC break — the real risk in this release. PR #9 added : void to methods to silence PHP 8.4's implicit-return-type deprecation. Callers are unaffected, but if you have overridden any of these methods in your own handlers or extended the bundle's classes without : void, you'll now hit a fatal Declaration must be compatible error. Grep your codebase before upgrading:
grep -rn "function .*(.*)\s*{" app/ src/ --include=*.php | grep -v ": void"
Symfony 8 support likely tightened the constraint floor. Symfony 8 requires PHP 8.4. If your app is still on Symfony 6.x or PHP 8.1/8.2, composer update will resolve to 1.1.1 or fail. Pin explicitly: "nutgram/symfony-bundle": "^1.2" and check composer why-not if it refuses.
This is not installable in Laravel. Trying to register NutgramBundle in bootstrap/providers.php will fail — it extends Symfony's Bundle, which Laravel's provider registrar does not understand. If a tutorial told you otherwise, it assumed nutgram/laravel.
Container caching hides config changes. After editing handler tags or bundle config, Symfony's compiled container may serve stale registrations. Always bin/console cache:clear (and cache:warmup in prod). The Laravel analogue: php artisan optimize:clear.
Debugging "handler not firing":
bin/console debug:container --tag=nutgram.handler — is your class listed?final/readonly in a way that blocks autoconfiguration? Check services.yaml _instanceof rules.hook: true mode but Telegram still pointed at the old URL? Re-run nutgram:hook:set.allowed_updates set in config and excluding your update type?Webhook secret token validation is easy to leave off. If hook.secret_token is unset, anyone who guesses your URL can POST forged updates. Set it and confirm the bundle verifies X-Telegram-Bot-Api-Secret-Token — worth a manual read of the controller in 1.2.0 rather than trusting defaults.
Extension points: the bot container is buildable via the bundle's compiler pass; for behavior the bundle doesn't expose (custom middleware, per-chat rate limiting), decorate the Nutgram service in services.yaml rather than forking. In Laravel, use a singleton + extend.
Version-pinning advice: hold at ^1.2 for Symfony 8 / PHP 8.4 projects. On mixed-version monorepos, let the Symfony-side app carry 1.2.0 and keep the Laravel-side app on nutgram/laravel independently — the two rarely need to move together.
How can I help you explore Laravel packages today?