Broadcasts and System-Wide Events
Broadcast\BroadcastDispatcher is the equivalent of Android's
BroadcastReceiver mechanism: it announces system-wide events (not
a direct reply to a specific Update) to every interested plugin —
without the sender and receiver knowing about each other directly.
Creating an Event
Any plain object (POJO) can be an event; it just needs to be passed
to publish():
namespace Aicrion\Tandroid\Broadcast\Event; final class UserJoinedEvent { public function __construct( public readonly int $userId, public readonly int $chatId, ) {} }
Writing a Receiver
use Aicrion\Tandroid\Attribute\BroadcastFilter; use Aicrion\Tandroid\Broadcast\BroadcastReceiverInterface; use Aicrion\Tandroid\Broadcast\Event\UserJoinedEvent; use Aicrion\Tandroid\Api\Telegram; #[BroadcastFilter(event: UserJoinedEvent::class)] final class WelcomeReceiver implements BroadcastReceiverInterface { public function onReceive(object $event): void { /** @var UserJoinedEvent $event */ Telegram::message()->to($event->chatId)->text('Welcome to the community! 🎉')->send(); } }
Like any Activity, a Receiver must be declared in the plugin's
manifest.php:
return Manifest::define( package: 'greeter', version: '1.0.0', receivers: [WelcomeReceiver::class], // ... );
How It Works
- On
Kernel::boot(),BroadcastDispatcher::registerReceivers()is called with every Receiver collected from all manifests. For each Receiver, its#[BroadcastFilter]is read via Reflection and stored in an internal registry (event class => [receiver classes]). - Whenever your code calls
BroadcastDispatcher::publish($event), the dispatcher resolves every Receiver registered for$event::classfrom the DI Container and runsonReceive($event)on each of them.
Built-in Event: UserJoinedEvent
Kernel::handle() publishes this event itself: with every Update, it
checks whether the chat_id has already been seen in cache (key
aicrion.seen_chat.{chat_id}); if it's the first time,
UserJoinedEvent is published and the chat is marked as seen. This
means any plugin can react to a "new user" without touching the
framework core — just write a Receiver for UserJoinedEvent.
Publishing Your Own Events
From inside any Activity or service that has access to
BroadcastDispatcher (by type-hinting it in the constructor — it's
autowired automatically):
final class OrderActivity extends BotActivity { public function __construct( private readonly BroadcastDispatcher $broadcaster, ) {} public function onCreate(Intent $intent): ?NavigationRequest { // ... order placement logic $this->broadcaster->publish(new OrderPlacedEvent(orderId: $order->id)); $this->setContentView(View::message('Your order has been placed ✅')); return null; } }
Current Limitation
The Broadcast system is currently synchronous — publish() runs
every Receiver immediately, within the same request. For heavy
operations (sending email, long-running processing) that shouldn't
slow down the reply to the user, write your Receiver to dispatch the
heavy work to a separate queue (e.g. Symfony Messenger, already
available as a dependency) and return quickly.