bentools/webpush-bundle
Symfony bundle to send Web Push notifications using the Web Push protocol. Manage user-to-subscription associations (multi-device and shared devices) with your own persistence (Doctrine or custom). Includes VAPID key generation and backend APIs for subscriptions.
Install the Bundle
composer require bentools/webpush-bundle
Requires PHP 8.1+.
Generate VAPID Keys
php bin/console webpush:generate:keys
Store the generated keys in config/packages/bentools_webpush.yaml:
bentools_webpush:
settings:
public_key: '%env(WEB_PUSH_PUBLIC_KEY)%'
private_key: '%env(WEB_PUSH_PRIVATE_KEY)%'
Create UserSubscription Entity
Implement BenTools\WebPushBundle\Model\Subscription\UserSubscriptionInterface (see example).
Register the Subscription Manager
Create a service implementing UserSubscriptionManagerInterface and tag it:
services:
App\Services\UserSubscriptionManager:
arguments: ['@doctrine']
tags:
- { name: bentools_webpush.subscription_manager, user_class: 'App\Entity\User' }
Frontend Integration
Use webpush-client to handle subscription/unsubscription via the /webpush endpoint.
use BenTools\WebPushBundle\Sender\PushMessageSender;
use BenTools\WebPushBundle\Model\Message\PushNotification;
// In a service/controller:
$sender = $this->container->get(PushMessageSender::class);
$notification = new PushNotification(
'Order #123 Shipped!',
'Your order has been dispatched.',
['icon' => '/images/notification-icon.png']
);
// Send to a user's subscriptions
$sender->send($notification, $user);
Frontend
webpush-client (POST to /webpush).UserSubscriptionManager.Backend
UserSubscriptionManagerRegistry to fetch subscriptions for a user:
$subscriptions = $registry->getManager($user)->findByUser($user);
Sending Notifications
PushNotification object and use PushMessageSender:
$sender->send($notification, $user); // Sends to all subscriptions
$sender->send($notification, $subscription); // Sends to a specific subscription
Event-Driven Notifications
Listen to domain events (e.g., OrderPlaced) and dispatch notifications:
public static function getSubscribedEvents()
{
return [OrderEvents::PLACED => 'notifyOrderPlaced'];
}
public function notifyOrderPlaced(OrderEvent $event)
{
$notification = new PushNotification(...);
$this->sender->send($notification, $event->getOrder()->getCustomer());
}
Batch Processing
Use PushMessageSender::sendBatch() for bulk notifications (e.g., newsletters):
$sender->sendBatch([$notification1, $notification2], $user);
Custom Metadata
Pass metadata via $options in UserSubscriptionManager::factory() (e.g., device type, browser):
$subscription = $manager->factory($user, $hash, $subscriptionData, ['device' => 'mobile']);
VAPID Key Management
parameters.yml for keys.webpush:generate:keys).Subscription Hash Collisions
UserSubscriptionManager::hash() produces unique hashes for endpoints.md5($endpoint) may suffice, but consider adding user context (e.g., md5($endpoint . $user->getId())).Apple Push Notification Service (APNs)
subject in config to be a URL or mailto: (e.g., https://yourdomain.com).bentools_webpush:
settings:
subject: 'https://yourdomain.com'
Frontend CORS Issues
/webpush endpoint is accessible via CORS if frontend and backend are on different domains.nelmio_cors_bundle).Subscription Expiry
// Example: Delete subscriptions older than 30 days
$qb = $manager->getRepository(UserSubscription::class)->createQueryBuilder('us');
$qb->delete('us', 'us')
->where('us.createdAt < :date')
->setParameter('date', new \DateTime('-30 days'));
$qb->getQuery()->execute();
Check Subscription Validity
Use webpush-client to test subscriptions before sending:
const subscription = await navigator.serviceWorker.ready.then(r => r.pushManager.subscription);
const response = await fetch('/webpush', {
method: 'POST',
body: JSON.stringify(subscription),
headers: {'Content-Type': 'application/json'}
});
console.log(await response.json());
Log Failed Sends
Enable debug mode in config/packages/bentools_webpush.yaml:
bentools_webpush:
debug: true
Check logs for errors like invalid subscriptions or VAPID key issues.
Custom Notification Types
Extend PushNotification or create a decorator for advanced payloads (e.g., interactive notifications):
class InteractiveNotification extends PushNotification
{
public function __construct(string $title, string $body, array $actions = [])
{
parent::__construct($title, $body, ['actions' => $actions]);
}
}
Priority Queues Use Symfony Messenger or a queue system (e.g., RabbitMQ) to defer non-critical notifications:
$message = new SendPushNotification($notification, $user);
$this->messageBus->dispatch($message);
Analytics Track notification delivery and user engagement:
// In a subscriber:
public function onNotificationSent(NotificationSentEvent $event)
{
$this->analytics->track($event->getUser(), 'notification_sent', $event->getData());
}
Fallback for Unsupported Browsers Detect unsupported browsers (e.g., Safari without APNs) and fall back to email or in-app notifications:
if (!$this->isBrowserSupported($request)) {
$this->fallbackNotifier->notify($user, $notification);
}
How can I help you explore Laravel packages today?