symfony/zulip-notifier
Symfony Notifier integration for Zulip. Configure via a zulip:// DSN using your Zulip email, token, host, and default channel, then send notifications to Zulip streams through Symfony’s notifier system.
Install the Package
Add the package to your composer.json:
composer require symfony/zulip-notifier
For Laravel, ensure compatibility by using a Symfony-compatible HTTP client (e.g., guzzlehttp/guzzle or symfony/http-client).
Configure the DSN
Add the Zulip DSN to your .env:
ZULIP_DSN=zulip://your-email@example.com:API_TOKEN@your-zulip-host.com?channel=your-channel
Or define it in config/services.php:
'zulip' => [
'dsn' => env('ZULIP_DSN', 'zulip://default:token@host?channel=default'),
],
First Notification Use the notifier in a Laravel controller or command:
use Symfony\Component\Notifier\Notifier;
use Symfony\Component\Notifier\Message\ChatMessage;
public function sendZulipNotification()
{
$notifier = new Notifier(
new ZulipTransport(config('zulip.dsn'))
);
$message = new ChatMessage('Hello from Laravel!');
$notifier->send($message);
}
Trigger via Events Dispatch a Laravel event and listen for it:
// In a service or controller
event(new DeploymentFailed('Server crashed!'));
// In EventServiceProvider
protected $listen = [
DeploymentFailed::class => [ZulipEventListener::class],
];
Event-Driven Notifications
// Listen to a custom event
public function handle(DeploymentFailed $event)
{
$notifier = app(Notifier::class);
$message = new ChatMessage($event->message);
$notifier->send($message);
}
Queue-Based Delays
Queue::push(function () {
$notifier = app(Notifier::class);
$notifier->send(new ChatMessage('Scheduled task completed!'));
});
Dynamic Channel Routing
$channel = $user->role === 'admin' ? 'admin-alerts' : 'user-notifications';
$dsn = "zulip://email:token@host?channel={$channel}";
$notifier->send(new ChatMessage('Alert!', $dsn));
Rich Message Formatting
$message = new ChatMessage(
"🚨 **Deployment Failed**\n```\n{$event->error}\n```",
config('zulip.dsn')
);
Symfony Notifier Bridge:
Extend Laravel’s Illuminate\Contracts\Queue\ShouldQueue for async notifications:
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Notifier;
class ZulipNotification implements ShouldQueue
{
public function handle()
{
$notifier = new Notifier(new ZulipTransport(config('zulip.dsn')));
$notifier->send(new ChatMessage($this->message));
}
}
Webhook Listeners: For incoming Zulip webhooks (e.g., reactions), create a Laravel route:
Route::post('/zulip/webhook', function (Request $request) {
// Parse Zulip payload and trigger Laravel logic
});
Testing:
Mock the ZulipTransport in PHPUnit:
$transport = $this->createMock(ZulipTransport::class);
$transport->expects($this->once())->method('send');
$notifier = new Notifier($transport);
Symfony Dependency Conflicts
HttpClient or OptionsResolver.// Use Guzzle instead of Symfony's HttpClient
$client = new Client(['base_uri' => 'https://your-zulip-host.com']);
$transport = new ZulipTransport($client, config('zulip.dsn'));
DSN Parsing Errors
channel query param).if (!str_starts_with(config('zulip.dsn'), 'zulip://')) {
throw new \RuntimeException('Invalid Zulip DSN');
}
Rate Limiting
Illuminate\Queue\Retryable:
class ZulipNotification implements ShouldQueue
{
public function retryUntil()
{
return now()->addMinutes(10);
}
}
Authentication Failures
try {
$notifier->send($message);
} catch (\Exception $e) {
Log::error("Zulip notification failed: {$e->getMessage()}");
// Send fallback email
}
Enable Debug Mode: Configure the transport to log requests:
$transport = new ZulipTransport($client, config('zulip.dsn'), [
'debug' => true,
]);
Check Zulip API Status:
Verify Zulip’s API endpoint (/api/version) is reachable:
$response = $client->request('GET', '/api/version');
if ($response->getStatusCode() !== 200) {
throw new \RuntimeException('Zulip API unavailable');
}
Custom Message Types
Extend ChatMessage for Zulip-specific features (e.g., topics, emoji):
class ZulipMessage extends ChatMessage
{
public function __construct(string $content, string $dsn, ?string $topic = null)
{
parent::__construct($content, $dsn);
$this->topic = $topic;
}
}
Transport Decorators Add pre/post-processing to messages:
class LoggingZulipTransport implements TransportInterface
{
private $transport;
public function __construct(ZulipTransport $transport)
{
$this->transport = $transport;
}
public function send(MessageInterface $message)
{
Log::info("Sending to Zulip: {$message->getContent()}");
$this->transport->send($message);
}
}
Multi-Channel Support Dynamically switch channels based on user roles:
$dsn = str_replace(
'?channel=default',
"?channel={$user->zulipChannel}",
config('zulip.dsn')
);
Environment Variables:
Use Laravel’s env() helper to load the DSN:
'zulip' => [
'dsn' => env('ZULIP_DSN', 'zulip://default:token@host'),
],
Channel Validation: Ensure the channel exists in Zulip before sending:
$response = $client->request('GET', '/api/channels');
$channels = json_decode($response->getContent(), true);
if (!in_array($channel, array_column($channels, 'name'))) {
throw new \RuntimeException("Channel {$channel} does not exist");
}
How can I help you explore Laravel packages today?