davibennun/laravel-push-notification
Laravel package for sending push notifications to iOS and Android devices via APNs and GCM/FCM. Integrates with Laravel's queue, config, and service container, letting you dispatch notifications to one or many devices through a simple, unified API.
Reality check first: this package is archived and its underlying transports are dead (APNs binary protocol retired Nov 2020, GCM shut down 2019). Treat the steps below as inheritance instructions — how to read, run, and safely extract value from an existing install — not as a greenfield setup.
Install (legacy constraints). The package pins old illuminate/support ranges, so on a modern Laravel app composer require davibennun/laravel-push-notification will fail dependency resolution. Options, in order of preference:
kreait/laravel-firebase (FCM v1) and an HTTP/2 APNs client.Register it (Laravel ≤5.4, or when package discovery is disabled):
// config/app.php
'providers' => [
Davibennun\LaravelPushNotification\LaravelPushNotificationServiceProvider::class,
],
'aliases' => [
'PushNotification' => Davibennun\LaravelPushNotification\Facades\PushNotification::class,
],
Publish config and drop credentials:
php artisan vendor:publish --provider="Davibennun\LaravelPushNotification\LaravelPushNotificationServiceProvider"
# older versions: --tag="config"
// config/pushnotification.php
'apps' => [
'appNameIOS' => [
'environment' => 'development', // 'production' for release builds
'certificate' => base_path('storage/certs/ios/apns.pem'), // combined cert + key
'passPhrase' => env('APNS_PASSPHRASE'),
'service' => 'apns',
],
'appNameAndroid' => [
'environment' => 'production',
'apiKey' => env('GCM_API_KEY'), // legacy Firebase server key
'service' => 'gcm',
],
],
Where to look first. Search the codebase for PushNotification:: — every send is a fluent chain, so grep -rn "PushNotification::app" app/ gives you the complete surface area. Then read config/pushnotification.php to map app aliases to platforms.
First use case — the canonical "notify one device" call:
use PushNotification;
PushNotification::app('appNameIOS')
->to($deviceToken)
->send('Your order has shipped');
For a richer payload, build a Message instead of passing a string:
$message = PushNotification::Message('Your order has shipped', [
'badge' => 1,
'sound' => 'default',
'custom' => ['order_id' => $order->id, 'screen' => 'orders'],
]);
PushNotification::app('appNameIOS')->to($deviceToken)->send($message);
Wrap it behind a domain service immediately. The single highest-value refactor on an inherited install is to hide the facade behind one interface, so the eventual migration is a one-file change:
interface PushNotifier
{
public function toDevice(string $token, string $platform, string $title, array $data = []): void;
}
class LegacyPushNotifier implements PushNotifier
{
public function toDevice(string $token, string $platform, string $title, array $data = []): void
{
$app = $platform === 'ios' ? 'appNameIOS' : 'appNameAndroid';
PushNotification::app($app)
->to($token)
->send(PushNotification::Message($title, ['custom' => $data]));
}
}
Then swap the binding in AppServiceProvider when you move to FCM/APNs HTTP/2 — call sites never change.
Always send from a queued job. ->send() opens a socket to Apple/Google and blocks. In a request cycle this is a latency bug waiting to happen, and with a DeviceCollection it's N sequential socket writes:
class SendPush implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(public int $userId, public string $text) {}
public function handle(PushNotifier $push): void
{
UserDevice::where('user_id', $this->userId)->chunk(500, function ($devices) use ($push) {
foreach ($devices as $device) {
try {
$push->toDevice($device->token, $device->platform, $this->text);
} catch (\Throwable $e) {
report($e); // never let one bad token kill the batch
}
}
});
}
}
Fan-out with DeviceCollection when you're pushing to a group (topic-ish behaviour, e.g. "all users on this order"):
$devices = PushNotification::DeviceCollection([
PushNotification::Device($tokenA, ['badge' => 3]),
PushNotification::Device($tokenB),
]);
PushNotification::app('appNameIOS')
->to($devices)
->send(PushNotification::Message('Flash sale starts now'));
Store tokens properly. One row per device, not per user — people have a phone and a tablet:
user_devices: id, user_id, platform ('ios'|'android'), token (unique), app_env, last_seen_at
Index on token and make it unique. Prune on delivery failures (APNs 410 Unregistered, GCM NotRegistered) — the package won't do this for you.
Multi-environment app aliases. Because APNs sandbox and production tokens are not interchangeable, define separate config entries (appNameIOS_dev, appNameIOS_prod) and pick based on app()->environment(). Same for an Android staging entry pointing at a separate Firebase project.
Testing. Bind a fake rather than hitting the network:
PushNotification::shouldReceive('app')->once()->andReturnSelf();
PushNotification::shouldReceive('to')->andReturnSelf();
PushNotification::shouldReceive('send')->once();
Better: bind a FakePushNotifier collecting toDevice() calls, and assert against that — it survives the migration.
Integrate with Laravel's notification system if you're modernizing incrementally: keep the package as the transport inside a custom NotificationChannel, so business code uses $user->notify(new OrderShipped($order)) and the legacy detail stays quarantined in one channel class.
The package cannot reach current endpoints. This is the blocking issue, not a footnote. Apple only serves HTTP/2 (api.push.apple.com) with .p8 token auth or modern .p12 certs; Google only serves FCM v1 (fcm.googleapis.com/v1/projects/.../messages:send) with OAuth2 service-account credentials. Any install still "working" is either pointed at a proxy, a private endpoint via the Android url config override, or silently failing. Verify with a real device before you promise anything.
Migration targets, in order of fit:
kreait/laravel-firebase (v1 API, service account JSON).edamov/pushok or laravel-notification-channels/apn.laravel-notification-channels/webpush.laravel-notification-channels family, so via() returns ['apn', 'fcm'].Certificate format is the #1 onboarding trap. APNs needs a single .pem containing both the certificate and the private key, plus the correct passphrase. Convert with:
openssl pkcs12 -in apns.p12 -out apns.pem -nodes -clcerts
Use an absolute path (base_path(...)) — a relative path resolves against the PHP working directory, which differs between CLI and FPM.
config:cache bites twice. Editing a cert path or passphrase after php artisan config:cache does nothing until php artisan config:clear. Same for .env values consumed at config-load time.
Environment mismatch produces silent no-ops. Sandbox tokens against production, or vice versa, return errors that are easy to swallow. The environment key must match the build that generated the token; dev/TestFlight builds are sandbox, App Store builds are production.
Payload limits and typing. The legacy binary protocol caps payloads around 256 bytes (2 KB/4 KB only for newer specs); an oversized message is rejected, not truncated. Keep badge an integer, sound a string filename on the device or 'default', and put all app data under custom — a flat top-level array will be silently dropped.
No retries, no backoff, no connection pooling. send() throws on connection failure; one dead token in a collection can abort the whole call depending on version. Always wrap per-device and report().
Token feedback/pruning is on you. Any APNs feedback capability in the underlying library is obsolete; instead, treat 410/NotRegistered responses as delete signals and log the raw response so you can inspect error codes.
Facade discovery. On Laravel 5.5+ with dont-discover configured, the alias disappears and you get Class 'PushNotification' not found at runtime — check composer.json extra.laravel.dont-discover and config/app.php before debugging anything else.
Extension points are thin by design. Message and Device are simple value objects you can keep reusing in your own adapter; the transport layer is not worth extending. The real extension point is the service binding described above — treat it as the seam you'll cut along during migration, and avoid letting PushNotification:: leak into controllers, models, or Blade.
How can I help you explore Laravel packages today?