w3bdeveloper7 / laravel-dynamic-notifier
Dynamic, permission-aware and resolver-driven notifications for Laravel.
Package info
github.com/W3bDeveloper7/laravel-dynamic-notifier
pkg:composer/w3bdeveloper7/laravel-dynamic-notifier
Requires
- php: ^8.2
- illuminate/bus: ^11.0|^12.0
- illuminate/cache: ^11.0|^12.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/database: ^11.0|^12.0
- illuminate/log: ^11.0|^12.0
- illuminate/queue: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.0
Suggests
- kreait/laravel-firebase: Required to use KreaitFcmTransport for real FCM push delivery.
This package is not auto-updated.
Last update: 2026-05-29 13:06:52 UTC
README
Dynamic, permission-aware, resolver-driven notification package for Laravel with hybrid storage support.
Install
composer require w3bdeveloper7/laravel-dynamic-notifier php artisan vendor:publish --tag=dynamic-notifier-config php artisan vendor:publish --tag=dynamic-notifier-migrations php artisan migrate
For Laravel-native mirroring, ensure the Laravel notifications table exists:
php artisan notifications:table php artisan migrate
What you get
- Dynamic recipient resolution by resolver key.
- Permission-based targeting through a pluggable backend contract.
- Preconfigured channel drivers:
database,log,fcm(pluggable transport). - Queue-ready dispatch with retries/backoff from config.
- Idempotency protection for duplicate sends.
- Fail-closed behavior when permission backend is not configured.
- Hybrid storage: package audit tables + optional Laravel notifications bridge.
Storage modes
Configure in config/dynamic-notifier.php:
| Mode | Package tables | Laravel notifications table |
|---|---|---|
canonical |
Source of truth | No |
laravel |
No | Source of truth |
hybrid (default) |
Source of truth | Mirrored after successful send |
'storage' => [ 'mode' => env('DYNAMIC_NOTIFIER_STORAGE_MODE', 'hybrid'), ], 'bridge' => [ 'enabled' => env('DYNAMIC_NOTIFIER_BRIDGE_ENABLED', false), 'write_to_laravel_notifications' => true, 'via_notification_class' => null, // optional custom notification class name ],
Reliability rules
- In
hybridmode, package tables remain the canonical audit source. - Bridge failures do not fail canonical delivery; errors are stored in delivery
payload.bridge_error. - In
laravel-only mode, bridge failures fail the job. - Idempotency applies to both canonical dispatch and Laravel bridge writes.
Architecture
flowchart LR
AppEvent[AppEvent] --> DynamicNotifier
DynamicNotifier --> ResolverPipeline[ResolverPipeline]
ResolverPipeline --> CanonicalStore[CanonicalStore]
CanonicalStore --> QueueJob[QueueJob]
QueueJob --> ChannelDriver[ChannelDriver]
ChannelDriver --> DeliveryLog[DeliveryLog]
ChannelDriver --> BridgeDecision{BridgeEnabled}
BridgeDecision -->|yes| LaravelBridge[LaravelBridge]
BridgeDecision -->|no| EndNoBridge[EndNoBridge]
LaravelBridge --> LaravelTable[LaravelNotificationsTable]
Loading
Quick usage
use W3bDeveloper7\DynamicNotifier\Contracts\DynamicNotifierInterface; use W3bDeveloper7\DynamicNotifier\DTO\NotificationContext; $notifier = app(DynamicNotifierInterface::class); $notifier->dispatch('booking_confirmed', new NotificationContext( entity: $booking, replacements: ['user_name' => $booking->bookedByUser->name], extra: ['action_id' => $booking->id] ), [ 'title' => 'Booking confirmed', 'message' => 'Hi :user_name, your booking is confirmed.', ]);
Configuring notification types
config/dynamic-notifier.php
'notification_types' => [ 'booking_confirmed' => [ 'sends' => [ [ 'resolver' => 'permission', 'channels' => ['database', 'fcm'], 'config' => [ 'permissions' => ['bookings.show', 'bookings.create'], ], 'title' => 'Booking confirmed', 'message' => 'A booking has been confirmed', 'priority' => 'high', ], ], ], ],
Laravel bridge requirements
When mirroring to Laravel notifications, recipients must include notifiable metadata:
'recipients' => [ [ 'id' => 1, 'meta' => [ 'notifiable_type' => App\Models\User::class, 'notifiable_id' => 1, ], ], ],
Plug your permission system
Implement:
W3bDeveloper7\DynamicNotifier\Contracts\PermissionBackendInterface
And bind it in your app service provider:
use W3bDeveloper7\DynamicNotifier\Contracts\PermissionBackendInterface; $this->app->singleton(PermissionBackendInterface::class, App\Notifications\MyPermissionBackend::class);
If you do not bind this contract, permission resolver returns empty recipients (safe default).
Pass center scope via context:
new NotificationContext( extra: ['center_ids' => [$booking->center_id]] );
Edge Back-End integration
When used in Edge Back-End, Modules\Admin\App\Adapters\AdminPermissionBackendAdapter is bound automatically. It reuses Admin::withPermissions() and center scoping (manage_all_centers + managedCenters).
FCM uses KreaitFcmTransport via NotificationServiceProvider when Kreait Firebase is available.
FCM transport
FcmDriver delegates to FcmTransportInterface:
| Transport | Class | When to use |
|---|---|---|
| Log (default) | LogFcmTransport |
Local/dev, package tests |
| Kreait | KreaitFcmTransport |
Production with kreait/laravel-firebase |
DYNAMIC_NOTIFIER_FCM_TRANSPORT=W3bDeveloper7\DynamicNotifier\Support\KreaitFcmTransport
Or bind in a service provider:
$this->app->singleton(FcmTransportInterface::class, KreaitFcmTransport::class);
Events
| Event | When |
|---|---|
NotificationDispatched |
Jobs queued for a notification rule |
NotificationDelivered |
Channel driver finished (sent or failed) |
BridgeFailed |
Laravel notifications mirror failed in hybrid mode |
Listen for observability, metrics, or custom audit hooks.
Service contract
Inject DynamicNotifierInterface (or use the DynamicNotifier facade) for easier mocking in tests.
Migration guidance
New project (recommended)
- Set
storage.modetohybrid. - Publish and run package migrations.
- Run
php artisan notifications:table && php artisan migrate. - Implement
PermissionBackendInterfacefor your RBAC system.
Existing Laravel notifications app
- Start with
storage.mode = laravelto mirror only to native table. - Move to
hybridwhen you want delivery audit/history in package tables.
Analytics-first setup
- Use
storage.mode = canonical. - Disable bridge:
DYNAMIC_NOTIFIER_BRIDGE_ENABLED=false.
Idempotency
Duplicate sends are suppressed using cache-backed idempotency keys:
- Pass an explicit key:
['idempotency_key' => 'booking:123']in the dispatch payload. - Or rely on implicit dedup via
action_idin contextextra:new NotificationContext(extra: ['action_id' => $booking->id]).
Keys are scoped per recipient and channel. Idempotency is checked at dispatch time (skip duplicate jobs) and again in the queued job (skip duplicate delivery after success).
Testing
composer install
composer test
Notes
- Default FCM transport logs payloads only; swap to
KreaitFcmTransportfor real push delivery. - CI runs
composer testandcomposer linton PHP 8.2/8.3 with Laravel 11/12.