richsage/rms-push-notifications-bundle
RMS Push Notifications Bundle is a Laravel/PHP package for sending push notifications to mobile devices via providers such as APNs and FCM, with a simple API for managing devices, payloads, and delivery.
First, orient yourself to what this actually is. Despite living in a repo that surfaces in "PHP/Laravel push" searches, richsage/rms-push-notifications-bundle is a Symfony 2 bundle that has been archived ("NOT MAINTAINED"). There is no service provider, no facade, no Illuminate\Notifications channel, and no modern transport code. Before you write a single line against it, decide which of two jobs you are actually doing:
For Laravel readers: this is a reading assignment, not a dependency. Do not add it to composer.json. If you want production push today, reach for kreait/laravel-firebase (FCM HTTP v1), Laravel's built-in notification channels, or a managed provider (OneSignal, SNS, Pusher Beams, Expo) that owns APNs/FCM for you.
Where to look first (in the source):
RMSPushNotificationsBundle.php — the bundle registration; tells you what the bundle wires up.DependencyInjection/RMSPushNotificationsExtension.php and Configuration.php — the config tree: provider credentials, per-platform settings, and which transport services get registered.Resources/config/*.xml (or .yml) — the service definitions. This is where you see one driver service per platform resolved from the container.Your first real-world use case. In an inherited app, the concrete first task is almost never "send a push" — it is "prove which send paths still reach a live provider." Trace one push end-to-end: find the call site in application code, follow it through the container to the per-platform service, and confirm the remote endpoint. Four of the five transports (C2DM, GCM, BlackBerry BIS, Windows Mobile MPNS) point at services that no longer exist. Only APNs is conceptually salvageable, and only by rewriting the client against Apple's HTTP/2 API with .p8 token auth.
In a greenfield Laravel project, the "first use case" is different: extract the shape, not the code. Define a PushSender / PushChannel contract, a message DTO (token, payload, badge, sound, collapse key, platform options), and a result object carrying per-token success/failure. Then implement two adapters — APNs HTTP/2 and FCM v1 — wrapped so they are swappable. That is roughly a two-to-four week exercise with maintained libraries, and it replaces everything this bundle pretends to offer.
Driver-per-platform, resolved from a container. The bundle's core pattern is a transport abstraction with one implementation per platform, each constructed from config-supplied credentials and selected at runtime. In a Laravel rewrite, this maps cleanly onto a manager/factory bound in a service provider with a config file (config/push.php) listing drivers; resolve by platform name, keep the concrete driver behind an interface so nothing downstream imports a vendor type.
Message DTO as the transport-agnostic unit. The valuable convention is a platform-neutral message object that each driver translates into its native payload. Per-platform differences are real and belong in the adapter, not in domain code: APNs wants aps.alert, badge, sound, and a JWT auth header; FCM v1 wants an OAuth2 service-account token and a structured message envelope. Your DTO carries the intersection; each adapter owns the platform-specific knobs (collapse key, priority, content-available, TTL).
Token registration as a first-class flow. The bundle assumes a token store keyed to a user and device. Replicate this as explicit lifecycle, which is the part teams routinely forget:
410 Unregistered, BadDeviceToken, FCM UNREGISTERED).Model tokens in their own table with user_id, platform, token, last_seen_at, invalidated_at, and an idempotency key for sends. Never overwrite a token silently; upsert with a uniqueness constraint and let pruning be a status change, not a delete, so you can audit.
Queue it, don't send inline. The legacy bundle's synchronous batch model (with per-request device limits) does not survive contact with modern fan-out. In Laravel, dispatch a job per batch, chunk token lists, and record per-token results in a send-audit table. This is also your observability line: delivery rate per platform, invalid-token rate, adapter error taxonomy.
Anti-corruption layer if you must keep it. If the bundle is live in a Symfony2 app and cannot be removed yet, isolate it behind your own PushSender interface and route application code through that. No domain class should call a bundle service directly. Give it an owner and a retirement date, and pin the Composer version (a fork in an internal org if you must patch anything) — assume no upstream fixes, ever.
Migration sequence that works. (1) Inventory platforms and OS floors actually in scope; confirm BlackBerry/Windows Mobile have no live requirement. (2) Define contracts and the DTO. (3) Implement APNs HTTP/2 and FCM v1 adapters over maintained libraries. (4) Build the token registry and pruning. (5) Cut over behind a feature flag, shadow-send in staging against APNs sandbox and FCM, then remove the legacy dependency. The bundle's platform list is a useful delete-checklist during step 5.
Most of the code paths are dead, not broken. C2DM was retired in 2012, GCM's legacy endpoints were shut down in 2019 (use FCM HTTP v1), BlackBerry BIS/BES push and Windows Mobile MPNS toast are both discontinued, and the APNs binary protocol is superseded by HTTP/2. You cannot fix these by patching URLs — the servers don't exist. Budget for a rewrite of the APNs client if that's the one path you need.
Do not bootstrap a Symfony ContainerBuilder inside a Laravel service provider. It's the tempting "temporary bridge" and it's a long-term tax: two DI containers, two config conventions, two lifecycles, and a debugging surface no one on the team owns. Lift the patterns, not the wiring.
Config quirks will bite you in a fork. Symfony2-era config trees, pre-PSR code style, and certificate-based APNs auth mean dated TLS/cipher assumptions. Modern requirements — PHP 8.x, TLS 1.2+, PSR-7/PSR-18 HTTP, .p8 JWT auth — are not satisfiable without forking essentially every file. If you fork, treat it as "we now own a push library," not "we patched a credential."
There are no tests and no CI. Archived + zero dependents means no fork momentum and no de facto maintainer to adopt patches from. Any incident is from-scratch debugging against provider docs that have largely been pulled down. Pin versions, keep a runbook, and record an ADR so the dependency isn't re-proposed.
Token hygiene is the silent failure mode. The classic symptoms — pushes "working" in staging, silently dropped in production — trace to stale tokens, a missing de-registration on logout, or not pruning on 410 Unregistered. Test the failure path: revoke a token at the provider, send, confirm your code marks it invalid and stops retrying it. Retrying a permanently invalid token is how you get throttled.
Extension points, if you inherit it. The pattern is to implement the transport interface and register a service definition in the bundle's container config. In a Laravel rewrite the equivalent is implementing your PushSender contract and adding a driver entry in config/push.php. Either way, keep platform-specific payload shaping inside the adapter and out of the caller.
Observability from day one. The legacy bundle gives you almost none. At minimum, log per-send: token id, platform, adapter, HTTP status/provider error code, and a correlation id. Build a dashboard for invalid-token rate by platform — it's your earliest signal that a provider API changed under you, which is exactly the class of failure this package's archived status guarantees you'll face.
The one genuinely reusable takeaway. Whether you build or buy, the requirements list is the same: provider-adapter abstraction, device-token registration/refresh/de-registration, multi-device fan-out, per-platform payload shaping, invalid-token pruning, and delivery observability. Hand that list to a vendor evaluation (FCM/APNs via a managed provider) rather than a ticket to integrate this bundle. For almost every team, push is a buy, not build decision — this package is the historical argument for why.
How can I help you explore Laravel packages today?