w3bdeveloper7/laravel-dynamic-notifier

Dynamic, permission-aware and resolver-driven notifications for Laravel.

Maintainers

Package info

github.com/W3bDeveloper7/laravel-dynamic-notifier

pkg:composer/w3bdeveloper7/laravel-dynamic-notifier

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-05-28 14:38 UTC

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 hybrid mode, 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)

  1. Set storage.mode to hybrid.
  2. Publish and run package migrations.
  3. Run php artisan notifications:table && php artisan migrate.
  4. Implement PermissionBackendInterface for your RBAC system.

Existing Laravel notifications app

  1. Start with storage.mode = laravel to mirror only to native table.
  2. Move to hybrid when you want delivery audit/history in package tables.

Analytics-first setup

  1. Use storage.mode = canonical.
  2. 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_id in context extra: 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 KreaitFcmTransport for real push delivery.
  • CI runs composer test and composer lint on PHP 8.2/8.3 with Laravel 11/12.