nti/notification-bundle
A notification bundle for PHP applications that gives you a structured way to compose and deliver user notifications across multiple channels. It plugs into your existing stack, keeps notification logic organized and reusable, and helps centralize messaging so alerts, updates, and transactional noti...
First, a reality check that shapes everything else: this is a Symfony bundle, not a Laravel package. The *Bundle suffix, the nti/ vendor namespace, and the DependencyInjection/Resources layout all point to Symfony. Under Laravel you won't get a working install by composer require-ing it — you either work in a Symfony app, or you treat it as a reference implementation to port. Given the score and the near-zero ecosystem, the port path is the realistic one.
If you are in Symfony:
composer require nti/notification-bundle (no version constraint documented; the last release is 2021-02-15, so expect a ^1.0-style tag).Nti\NotificationBundle\NotificationBundle in config/bundles.php.bin/console config:dump-reference notification # try nti_notification too
Where to look first, in this order:
src/DependencyInjection/Configuration.php — this file is the API surface. Every knob you can turn is defined here.src/DependencyInjection/*Extension.php — which services are public, which are tagged (that tag list tells you the extension points).src/Resources/config/services.* — service ids for the sender/manager you'll inject.composer.json — a guzzlehttp/guzzle, symfony/messenger, or swiftmailer entry tells you the transport style faster than any README.Tests/ and README.md — with 3 stars there is no blog coverage; tests are your only executable spec.First use case: one app sends one notification over one channel (log or database transport). Inject the manager service into a console command, send to a hardcoded recipient, and confirm delivery. Only then attempt the cross-application fan-out that the package description promises.
Channel-per-class. Expect an interface for transports (mail, SMS, push, DB) with implementations tagged in the container. To add a channel: implement the interface, register the service, and wire the tag in the bundle's compiler pass or a local extension. Don't fork the sender.
Separate rendering from delivery. Build the notification as a value object — subject, body/template, recipients, channel list — and hand it to the manager. This lets the same notification produce an email body and a DB row without duplicating logic, and it is the single design decision most worth carrying into Laravel.
Fan-out as the core workflow. "Sending notifications through different applications" means app A triggers, apps B and C receive. The interesting code is therefore outbound transport, not templating. Look for whether the bundle calls an HTTP endpoint, writes to a shared table, or dispatches a Messenger message; that determines how you'd integrate.
Queue everything. If a transport is a MessageHandler, dispatch a message rather than calling the manager synchronously and let messenger:consume own retries and backoff. This is also the pattern that maps 1:1 onto Laravel queues.
Event subscribers for cross-cutting concerns. Symfony bundles lean on EventDispatcherInterface. Subscribe to pre-send/post-send events for auditing and logging instead of decorating the sender — decoration breaks the moment someone injects the concrete class.
Laravel adaptation sketch. Bind a NotificationManager singleton in a service provider, put channels behind an interface resolved from config/notifications.php, back transports with Mail/Http/DB facades, and implement ShouldQueue. Keep the recipient+channel model; discard the Symfony config tree.
Testing. Point the sender at a fake channel that appends to an array; assert on the recorded notifications rather than mocking the container. This works in either framework and survives the port.
ContainerAwareCommand, private-by-default services, possibly Swiftmailer. On Symfony 6/7 expect deprecations or hard breaks. Pin the framework or budget a fork.src/ directly; resist searching.NotificationBundle and the config root key frequently differ. Get the exact name from buildTree()->getName() and confirm with config:dump-reference — guessing costs an hour of "unrecognized option."$container->get() fails on modern Symfony. Use autowiring/aliases from the extension; set public: true in a local override only for debugging.false/void when no channel matches or recipients are empty. Log around the manager call and verify a transport is actually configured for the channel before blaming the transport.bin/console debug:container and debug:event-dispatcher show whether your channel is tagged and your listener registered. In the Laravel port, the equivalent first move is a Log-backed channel.composer update will not deliver fixes, and the "opportunity" score here reflects an unserved domain (cross-app notifications), not a maintained dependency.How can I help you explore Laravel packages today?