symfony/firebase-notifier
Symfony Notifier bridge for Firebase Cloud Messaging. Configure via FIREBASE_DSN and send notifications with platform-specific options using AndroidNotification, IOSNotification, or WebNotification to customize icons, sounds, actions, and more.
Install Dependencies
Add to composer.json:
"require": {
"symfony/notifier": "^6.4",
"symfony/firebase-notifier": "^6.4"
}
Run composer install.
Configure Firebase DSN
Add to .env:
FIREBASE_DSN=firebase://USERNAME:PASSWORD@default
Replace USERNAME:PASSWORD with your Firebase service account credentials (or legacy FCM credentials).
First Notification
Create a notification class (e.g., app/Notifications/FirebasePushNotification.php):
use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Bridge\Firebase\Notification\AndroidNotification;
use Symfony\Component\Notifier\Bridge\Firebase\Transport;
class FirebasePushNotification
{
public function send(string $token, string $title, string $body): void
{
$message = new ChatMessage($body);
$message->options(
(new AndroidNotification())
->title($title)
->body($body)
->priority('high')
);
$chatter = new Transport(
new \Symfony\Component\Notifier\Transport\ChatTransport(
'firebase://' . env('FIREBASE_DSN')
)
);
$chatter->send($message, $token);
}
}
Use in Laravel Inject the class into a controller/service:
use App\Notifications\FirebasePushNotification;
class NotificationController extends Controller
{
public function __invoke(FirebasePushNotification $notifier)
{
$notifier->send(
'device_fcm_token_here',
'Hello!',
'This is your notification.'
);
}
}
Define Platform-Specific Options
Use AndroidNotification, IOSNotification, or WebNotification classes to customize payloads:
$androidOptions = (new AndroidNotification('/topics/news'))
->icon('ic_notification')
->sound('default')
->priority('high')
->data(['key' => 'value']);
$iosOptions = (new IOSNotification())
->badge(1)
->sound('default')
->mutableContent(true);
Attach Options to Message
$message = new ChatMessage('Your content here');
$message->options($androidOptions); // or $iosOptions
Send via Transport
$transport = new Transport(
new \Symfony\Component\Notifier\Transport\ChatTransport(
'firebase://' . env('FIREBASE_DSN')
)
);
$transport->send($message, $deviceToken);
Batch Sending (Topics) Target multiple devices via Firebase topics:
$message->options(
(new AndroidNotification('/topics/promotions'))
->priority('high')
);
Service Provider Binding
Bind the transport in AppServiceProvider:
public function boot()
{
$this->app->singleton('firebase.notifier', function () {
return new Transport(
new \Symfony\Component\Notifier\Transport\ChatTransport(
'firebase://' . env('FIREBASE_DSN')
)
);
});
}
Queueable Notifications Use Laravel Queues to defer sending:
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class FirebaseNotification implements ShouldQueue
{
use Queueable;
public function handle()
{
$notifier = app('firebase.notifier');
$notifier->send($this->message, $this->token);
}
}
Event-Driven Notifications Trigger notifications from Laravel events:
use Illuminate\Queue\Events\JobProcessed;
Event::listen(JobProcessed::class, function ($event) {
$notifier = app('firebase.notifier');
$notifier->send(
new ChatMessage('Job completed!'),
$event->user->device_token,
(new AndroidNotification())
->priority('high')
->title('Job Status')
);
});
Dynamic Payloads Use closures or factories to generate payloads:
$message = new ChatMessage('Order updated');
$message->options(
(new AndroidNotification())
->title('Order #' . $order->id)
->body('Status: ' . $order->status)
->data(['order_id' => $order->id])
);
Fallback for Unsupported Platforms Check platform before sending:
if (str_starts_with($token, 'APA')) { // Android
$message->options($androidOptions);
} elseif (str_starts_with($token, 'c2s')) { // iOS
$message->options($iosOptions);
}
Retry Logic Extend Symfony’s retry mechanism for Laravel:
$transport = new Transport(
new \Symfony\Component\Notifier\Transport\ChatTransport(
'firebase://' . env('FIREBASE_DSN'),
['max_retries' => 3, 'delay' => 1000]
)
);
DSN Format Sensitivity
@default) causes silent failures.if (!str_starts_with(env('FIREBASE_DSN'), 'firebase://')) {
throw new \RuntimeException('Invalid Firebase DSN');
}
Firebase Credential Deprecation
$serviceAccount = json_decode(file_get_contents('path/to/service-account.json'), true);
$dsn = sprintf(
'firebase://%s:%s@%s',
$serviceAccount['client_email'],
$serviceAccount['private_key'],
'default'
);
Payload Size Limits
data payloads for critical info and deep links for the rest:
$message->options(
(new AndroidNotification())
->data(['large_data' => json_encode($data)])
->clickAction('OPEN_DEEP_LINK')
);
Token Expiration
try {
$transport->send($message, $token);
} catch (\Symfony\Component\Notifier\Exception\TransportException $e) {
if ($e->getCode() === 401) { // Unauthorized (invalid token)
$token = $this->refreshDeviceToken($user);
retry($transport->send($message, $token), 3);
}
}
Platform-Specific Quirks
priority (high/normal) for notifications to appear.badge, sound, and content-available for silent pushes.notification payload type; use data for background sync.Enable Verbose Logging Configure Symfony’s Notifier to log raw payloads:
$transport = new Transport(
new \Symfony\Component\Notifier\Transport\ChatTransport(
'firebase://' . env('FIREBASE_DSN'),
['debug' => true]
)
);
Check logs for the exact payload sent to Firebase.
Test with Firebase Console Use the Firebase Console to verify tokens and simulate notifications.
Validate Tokens Ensure tokens are not empty or malformed:
if (empty($token) || !preg_match('/^[a-zA-Z0-9\-_]{1,}$/', $token)) {
throw new \InvalidArgumentException('Invalid FCM token');
}
Custom Notification Classes
Extend AndroidNotification, IOSNotification, or WebNotification for project-specific options:
class CustomAndroidNotification extends AndroidNotification
{
public function customAction(string $action): self
{
$this->data['custom_action'] = $action;
return $this;
}
}
Transport Decorators Wrap the transport to add
How can I help you explore Laravel packages today?