Looking to hire Laravel developers? Try LaraJobs

laravel-notification-foundation maintained by thesetj

Description
Foundation package for Laravel notifications providing base classes, contracts, traits, and channels for FCM, SMS, and database delivery.
Author
Last update
2026/07/30 12:07 (v1.2.0)
License
Links
Downloads
1

Comments
comments powered by Disqus

laravel-notification-foundation

A foundation package for building Laravel notifications. Instead of writing toDatabase(), toFcm(), and toSms() from scratch in every notification, you populate a shared data bag using fluent setters in the constructor — the package delivers it to each active channel automatically.

Channels are registered globally in a published service provider (similar to middleware in bootstrap/app.php) and can be toggled on or off per notification instance.

Installation

composer require thesetj/laravel-notification-foundation

Publish the service provider stub:

php artisan vendor:publish --tag=notification-foundation-provider

Register it in bootstrap/providers.php (Laravel 11+) or config/app.php:

App\Providers\NotificationFoundationServiceProvider::class,

The zero-override pattern

The default toDatabase(), toFcm(), and toSms() implementations read directly from what you set via the fluent API. For most notifications, no payload methods need to be written at all — the entire notification is just a constructor:

use NotificationFoundation\BaseNotification;

class OrderShippedNotification extends BaseNotification
{
    public function __construct(Order $order, User $recipient)
    {
        // Shared data is merged into every active channel's payload automatically
        $sharedData = [
            'order_id'   => (string) $order->id,
            'order_code' => $order->code,
            'user_name'  => $recipient->name,
        ];

        $this->setSharedData($sharedData)
             // FCM gets a tailored title and body on top of shared data
             ->setDataForFcmChannel([
                 'title' => 'Your order has shipped',
                 'body'  => "Order #{$order->code} is on its way.",
             ])
             // Database relies on shared data alone — no extra data needed
             // SMS is off by default in the service provider; turn it on for this notification
             ->useSmsChannel()
             ->setDataForSmsChannel([
                 'body' => "Hi {$recipient->name}, order #{$order->code} has shipped.",
             ])
             // This order does not trigger an email
             ->useEmailChannel(false);
    }
}

setSharedData() is merged into every active channel's payload automatically. Individual channels can add their own data on top, or rely on shared data alone. No toDatabase(), toFcm(), or toSms() methods needed.

Overriding a payload method

Override only what you need. The data bag and shared data are still available:

class OrderShippedNotification extends BaseNotification
{
    public function __construct(private Order $order)
    {
        $this->setSharedData(['order_id' => (string) $order->id]);
    }

    public function toFcm(mixed $notifiable): FcmMessage
    {
        return FcmMessage::create()
            ->title('Your order has shipped')
            ->body("Order #{$this->order->id} is on its way.")
            ->setData($this->getSharedData())
            ->android(['priority' => 'high'])
            ->apns(['headers' => ['apns-priority' => '10']]);
    }
}

Per-instance channel toggling

Every registered channel — built-in or custom — automatically gets fluent toggle methods:

(new OrderShippedNotification($order))
    ->useFcmChannel(false)
    ->useSmsChannel(true)
    ->useEmailChannel();        // any registered channel works the same way
$notification->enableSmsChannel()->disableFcmChannel();

The default active state per channel is set in the service provider (see below).

Channel registration

Edit the published App\Providers\NotificationFoundationServiceProvider. Comment out or remove what you don't need, and add your own channels:

public function boot(): void
{
    BaseNotification::registerChannel('database', 'database', true);
    BaseNotification::registerChannel('fcm', FcmChannel::class, true);
    BaseNotification::registerChannel('sms', SmsChannel::class, false);

    // Custom channels get the same toggle and data-setter API automatically
    // BaseNotification::registerChannel('mail', 'mail', false);
    // BaseNotification::registerChannel('slack', SlackChannel::class, false);
}

The third argument is the default state (true = active, false = inactive).

Driver binding

FcmChannel and SmsChannel delegate delivery to driver contracts. Bind your implementations in a service provider:

use NotificationFoundation\Contracts\FcmDriverContract;
use NotificationFoundation\Contracts\SmsDriverContract;

$this->app->bind(FcmDriverContract::class, YourFcmDriver::class);
$this->app->bind(SmsDriverContract::class, YourSmsDriver::class);

FCM driver

Implement FcmDriverContract. The contract's send() method receives either a single token string or an array of tokens, so it can serve as a gateway to whatever internal structure your provider requires:

use NotificationFoundation\Contracts\FcmDriverContract;
use NotificationFoundation\Messages\FcmMessage;

class MyFcmDriver implements FcmDriverContract
{
    public function send(FcmMessage $message, string|array $tokens): void
    {
        if (is_array($tokens)) {
            $this->sendMany($message, $tokens);
        } else {
            $this->sendOne($message, $tokens);
        }
    }

    private function sendOne(FcmMessage $message, string $token): void
    {
        // send $message->toArray($token) to the FCM HTTP v1 API
    }

    private function sendMany(FcmMessage $message, array $tokens): void
    {
        // use a batch API if your provider supports it, or loop sendOne()
        foreach ($tokens as $token) {
            $this->sendOne($message, $token);
        }
    }
}

SMS driver

Implement SmsDriverContract. The send() method can likewise act as a gateway — if your SMS provider supports bulk sending, add a private sendMany() and dispatch from send() based on whether $message->getTo() is a single number or a list:

use NotificationFoundation\Contracts\SmsDriverContract;
use NotificationFoundation\Messages\SmsMessage;

class MyProvider implements SmsDriverContract
{
    public function send(SmsMessage $message): void
    {
        // deliver $message->getContent() to $message->getTo()
        // optionally route to sendOne() / sendMany() based on recipient type
    }
}

FCM routing

Add routeNotificationForFcm() to your notifiable model:

public function routeNotificationForFcm($notification): string|array
{
    return $this->fcm_tokens; // single token or array for multi-device
}

SMS routing

Add routeNotificationForSms() to your notifiable model, or set the recipient directly on the message:

// On the notifiable model:
public function routeNotificationForSms($notification): string
{
    return $this->phone_number;
}

// Or in toSms():
return SmsMessage::create('Your OTP is 1234')->to($notifiable->phone);