Laravel Packagev7.0.0MIT License

Notification Dependencies

Notification Dependencies attaches Laravel database notifications to the Eloquent models they are about, and deletes them as soon as any one of those models is deleted. A "new comment on your order" notification disappears when the order or the comment is gone, so your users never open a notification that points at nothing.

Attach to Models

Name the models a notification depends on with dependsOn(), at send time or inside the notification class.

Automatic Cleanup

Deleting any dependency deletes every stored notification that depends on it, for every recipient.

Several Dependencies

One notification can depend on many models of different types, and is deleted when the first of them goes.

Queue Safe

Only each model's type and key travel through the queue, and a dependency deleted while the job waits still deletes the notification.

Soft Delete Aware

Choose whether a soft delete removes the notifications or only a force delete does.

Queryable

Every dependable model gets a dependentNotifications relation you can load, count, and filter.

Quick Tip

Only the database channel is affected. Mail, broadcast, and every other channel of the same notification are sent exactly as before.

#Installation

The package links rows of Laravel's own notifications table, so that table has to exist before you migrate.

#Requirements

  • PHP 8.0
  • Laravel 7.30 or newer on the 7.x line
  • The notifications table of Laravel's database channel

#Create the Notifications Table

Skip this step when your application already stores database notifications.

Terminal
php artisan notifications:table
php artisan migrate

#Composer Installation

Terminal
composer require pharaonic/laravel-notification-dependencies

The service provider is registered automatically through package discovery.

#Run Migrations

The package loads its migration automatically, so a plain migrate creates the notification_dependencies table:

Terminal
php artisan migrate

The migration stops with an explanation when the notifications table is missing, or when a table with the dependency table's name already exists.

#Publish Configuration

Publish the configuration only when you need to rename the table, accept any model as a dependency, or change the soft-delete behavior:

Terminal
php artisan vendor:publish --tag=notification-dependencies-config

The file is published to config/pharaonic/notification-dependencies.php. See Configuration.

#Publish the Migration (Optional)

You don't need to publish the migration to run it. Publish it only when you want a copy in database/migrations:

Terminal
php artisan vendor:publish --tag=notification-dependencies-migrations
Renaming the table

Set table in the published configuration before you run the migration. Changing it later does not rename an existing table.

Installation Complete

You're all set. Add HasDependentNotifications to your models and HasDependencies to your notifications, as shown in Basic Usage.

#Configuration

The defaults work without publishing anything. After vendor:publish --tag=notification-dependencies-config, the options live in config/pharaonic/notification-dependencies.php and are read under the pharaonic.notification-dependencies key.

config/pharaonic/notification-dependencies.php
1return [
2 'table' => 'notification_dependencies',
3 
4 'all_models' => false,
5 
6 'delete_on_soft_delete' => true,
7];

#Options

KeyDefaultDescription
tablenotification_dependenciesThe dependency table the migration creates and every query uses.
all_modelsfalseWhen true, any saved Eloquent model can be a dependency, without HasDependentNotifications.
delete_on_soft_deletetrueWhen true, soft deleting a dependency deletes its notifications. When false, only forceDelete() does.

#table

The migration creates the table with this name, and NotificationDependency maps to it. Set it before migrating: changing it after the migration ran does not rename the existing table.

#all_models

By default, a notification can only depend on models that use HasDependentNotifications, and only their deletions are checked. Turn all_models on to accept any model, such as a model from another package you can't edit:

config/pharaonic/notification-dependencies.php
1'all_models' => true,
Performance

With all_models on, every Eloquent model deletion in the application runs one indexed lookup on the dependency table. Models that use HasDependentNotifications are still cleaned up once, through their own event, and only they have the dependentNotifications() relation.

The setting is read on every use, so changing it at runtime (in a test, for example) applies to dependsOn() and to deletions alike.

#delete_on_soft_delete

By default, soft deleting a dependency deletes its notifications. Set it to false to keep them until the model is force deleted:

config/pharaonic/notification-dependencies.php
1'delete_on_soft_delete' => false,

See Soft Deletes for the details.

#Basic Usage

Using the package takes two traits: one on the models a notification can depend on, and one on the notification.

#1. Mark the Dependable Models

Add HasDependentNotifications to every model a notification can depend on:

app/Order.php
1namespace App;
2 
3use Illuminate\Database\Eloquent\Model;
4use Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependentNotifications;
5 
6class Order extends Model
7{
8 use HasDependentNotifications;
9}

You can skip this step by turning on all_models.

#2. Add HasDependencies to the Notification

app/Notifications/OrderCommented.php
1namespace App\Notifications;
2 
3use Illuminate\Notifications\Notification;
4use Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependencies;
5 
6class OrderCommented extends Notification
7{
8 use HasDependencies;
9 
10 public function via($notifiable)
11 {
12 return ['database'];
13 }
14 
15 public function toDatabase($notifiable)
16 {
17 return ['message' => 'Your order received a comment.'];
18 }
19}

#3. Name the Dependencies When You Send It

1$user->notify((new OrderCommented)->dependsOn($order));
2 
3$user->notify((new OrderCommented)->dependsOn($order, $comment));

#4. Delete a Dependency

A notification is deleted as soon as any of its dependencies is deleted:

What gets deleted
1Notification A depends on Order #123 and Comment #456
2Notification B depends on Order #123
3
4$comment->delete(); // deletes A and its links; B stays
5$order->delete(); // deletes B

Every recipient gets their own stored row, and each row gets its own links, so this works the same for Notification::send($users, ...):

1use Illuminate\Support\Facades\Notification;
2 
3Notification::send($order->watchers, (new OrderCommented)->dependsOn($order));
4 
5$order->delete(); // deletes the notification of every watcher
What is untouched

The dependency models themselves are never changed, and neither are the other channels of the notification. Only rows stored by the database channel are linked and deleted.

A notification without HasDependencies, or with the trait but without any dependency, is stored exactly as Laravel stores it.

#Declaring Dependencies

HasDependencies gives a notification two ways to name its dependencies. You can use either one, or both together.

#At Send Time with dependsOn()

dependsOn() takes any number of models and returns the notification, so you can chain it. Repeated calls add to the set, and a model that is already in it is ignored:

1$notification = (new OrderCommented)
2 ->dependsOn($order)
3 ->dependsOn($comment, $product)
4 ->dependsOn($order); // already there, ignored
5 
6$user->notify($notification);

dependsOn() without arguments does nothing, so you can spread an array that may be empty:

1$notification->dependsOn(...$optionalDependencies);

#In the Class with dependencies()

Override dependencies() to declare the models the notification always depends on. They are added to whatever dependsOn() received, without duplicates:

app/Notifications/OrderCommented.php
1use App\Comment;
2use App\Order;
3use Illuminate\Notifications\Notification;
4use Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependencies;
5 
6class OrderCommented extends Notification
7{
8 use HasDependencies;
9 
10 protected $order;
11 
12 protected $comment;
13 
14 public function __construct(Order $order, Comment $comment, array $dependencies = [])
15 {
16 $this->order = $order;
17 $this->comment = $comment;
18 $this->dependsOn(...$dependencies);
19 }
20 
21 public function dependencies(): array
22 {
23 return [$this->order, $this->comment];
24 }
25 
26 // via() and toDatabase()...
27}

dependencies() is read when the database channel stores the notification, so it sees the models the notification holds at that moment. It must return an array of Eloquent models.

#Calling dependsOn() in the Constructor

You can also call dependsOn() from the constructor. This keeps only the identities, not the models, which is what you want for a queued notification that doesn't need the models itself:

app/Notifications/InvoiceIssued.php
1public function __construct(Invoice $invoice)
2{
3 $this->number = $invoice->number;
4 $this->dependsOn($invoice);
5}

See Queued Notifications.

#What Can Be a Dependency

dependsOn() and dependencies() accept a model only when:

  • it uses HasDependentNotifications, or all_models is on;
  • it is saved ($model->exists is true);
  • its primary key is an integer or a non-empty string, at most 191 characters;
  • its morph type is at most 191 characters.

Anything else throws InvalidArgumentException with the reason, so a dependency can never be forgotten silently. dependsOn() throws right away; dependencies() throws when the notification is stored, and the stored row is deleted first.

Models are identified by their morph type (morph map aliases included) and primary key. Integer, UUID, and ULID keys share the same table.

#Dependable Models

HasDependentNotifications does two things for a model: it deletes the dependent notifications when the model is deleted, and it adds the dependentNotifications() relation.

app/Order.php
1use Illuminate\Database\Eloquent\Model;
2use Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependentNotifications;
3 
4class Order extends Model
5{
6 use HasDependentNotifications;
7}

#Cleanup on Deletion

The trait listens to the model's own deleted event. When notifications depend on the model, they are deleted with set-based queries in batches of 500 ids, without loading them, and their links go with them.

1$order->delete(); // every notification that depends on this order is gone
Bulk deletes

Order::where(...)->delete(), raw SQL, and database cascades fire no Eloquent events, so their notifications stay. Delete such models one by one when their notifications must go: Order::where(...)->get()->each->delete().

#The dependentNotifications Relation

dependentNotifications() is a many-to-many relation to Laravel's Illuminate\Notifications\DatabaseNotification, through the dependency table. Use it like any Eloquent relation:

1$order->dependentNotifications; // Collection of DatabaseNotification
2 
3$order->dependentNotifications()->whereNull('read_at')->count();
4 
5Order::with('dependentNotifications')->get();
6 
7Order::withCount('dependentNotifications')->get(); // dependent_notifications_count
8 
9Order::whereHas('dependentNotifications', function ($query) {
10 $query->whereNull('read_at');
11})->get();

The relation works with integer, UUID, and ULID keys on MySQL, PostgreSQL, and SQLite: the dependency table stores ids as strings, and the relation compares the model's key as a string.

With all_models

When all_models is on, models without the trait can be dependencies and are cleaned up on deletion, but only models with the trait have dependentNotifications(). Query the others through the NotificationDependency model.

#Soft Deletes

A dependency that uses SoftDeletes follows the delete_on_soft_delete setting.

#Default: Soft Delete Removes the Notifications

1$order->delete(); // soft delete: the dependent notifications are deleted
2$order->trashed(); // true

A model that is already soft deleted can't be a dependency in this mode: a notification that depends on it is deleted right after it is stored.

#Keep Them Until Force Delete

config/pharaonic/notification-dependencies.php
1'delete_on_soft_delete' => false,
1$order->delete(); // soft delete: the notifications stay
2$order->restore(); // still there
3$order->forceDelete(); // now they are deleted

In this mode a soft-deleted model still counts as existing, so a new notification can depend on it.

Restoring does not bring them back

With the default setting, restoring a soft-deleted model does not restore the notifications its soft delete removed. Use delete_on_soft_delete => false when restores are common in your application.

#Queued Notifications

Dependencies work the same for notifications that implement ShouldQueue.

app/Notifications/OrderShipped.php
1use App\Order;
2use Illuminate\Bus\Queueable;
3use Illuminate\Contracts\Queue\ShouldQueue;
4use Illuminate\Notifications\Notification;
5use Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependencies;
6 
7class OrderShipped extends Notification implements ShouldQueue
8{
9 use HasDependencies;
10 use Queueable;
11 
12 protected $reference;
13 
14 public function __construct(Order $order)
15 {
16 $this->reference = $order->reference;
17 $this->dependsOn($order);
18 }
19 
20 public function via($notifiable)
21 {
22 return ['database'];
23 }
24 
25 public function toDatabase($notifiable)
26 {
27 return ['message' => "Order {$this->reference} has shipped."];
28 }
29}

#What Travels Through the Queue

dependsOn() keeps only each model's connection, morph type, class, and key, never the model. A queued notification carries no extra models, and the worker doesn't query them back.

#A Dependency Deleted While the Job Waits

When the worker stores the notification, the package checks that every dependency still exists. If one was deleted in the meantime, the new notification is deleted right after the database channel stores it. A dependency whose class was renamed or removed since it was queued counts as deleted too.

#Notifications That Keep Their Own Models

A notification that keeps its own models with SerializesModels (for example to return them from dependencies()) needs those models back when the job runs. When one of them is gone, the job fails as usual, before anything is stored. Use dependsOn() in the constructor, as above, when the notification doesn't need the models themselves.

#Failures

Like any exception from a NotificationSent listener, a linking failure stops Laravel's sending at that notifiable, and the notification row is deleted with its links. A queued notification is then retried as Laravel retries any failed job.

#NotificationDependency Model

Pharaonic\Laravel\NotificationDependencies\Models\NotificationDependency maps the dependency table: one row per stored notification and model it depends on. You don't need it for everyday use, but it lets you query the links directly.

#The Table

ColumnTypeDescription
idbig incrementsPrimary key.
notification_iduuidThe notifications.id of the stored notification. Cascades on delete.
dependable_typestring (191)The dependency's morph type (its morph map alias, or class name).
dependable_idstring (191)The dependency's primary key, as a string.
created_at, updated_attimestampsWhen the link was written.

(notification_id, dependable_type, dependable_id) is unique, and (dependable_type, dependable_id) is indexed for the lookup on deletion.

1use Pharaonic\Laravel\NotificationDependencies\Models\NotificationDependency;
2 
3// Every link to this order, with its notification
4NotificationDependency::whereDependable($order)->with('notification')->get();
5 
6// The models one notification depends on
7NotificationDependency::where('notification_id', $notification->id)
8 ->with('dependable')
9 ->get()
10 ->pluck('dependable');

whereDependable() accepts any model, with or without HasDependentNotifications. A model that could never have been linked (no usable key, for example) matches nothing.

#Relations

MethodReturnsDescription
notification()BelongsToThe stored Illuminate\Notifications\DatabaseNotification.
dependable()MorphToThe model the notification depends on.
Writes

The package writes and deletes links itself. Deleting a notification deletes its links through the foreign key, so you don't need to manage rows of this table.

#API Reference

Every public name of the package. Classes in Support, Actions, Listeners, and Relations are internal.

#HasDependencies

Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependencies, for notifications.

MethodReturnsDescription
dependsOn(Model ...$models)staticAdds models the notification depends on. Duplicates are ignored. Throws InvalidArgumentException for a model that can't be a dependency.
dependencies()arrayOverride to return the models the notification always depends on. Defaults to [].

#HasDependentNotifications

Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependentNotifications, for dependable models.

MethodReturnsDescription
dependentNotifications()MorphToManyThe stored DatabaseNotification rows that depend on the model.

Deleting the model (or soft deleting it, unless delete_on_soft_delete is false) deletes those notifications.

#NotificationDependency

Pharaonic\Laravel\NotificationDependencies\Models\NotificationDependency

MemberTypeDescription
notification()BelongsToThe stored database notification.
dependable()MorphToThe model the notification depends on.
whereDependable(Model $model)scopeOnly the links to this model.
DEFAULT_TABLEconstantnotification_dependencies, used when table is not set.
notification_id, dependable_type, dependable_idcolumnsSee NotificationDependency Model.

#Configuration Keys

KeyDefault
pharaonic.notification-dependencies.tablenotification_dependencies
pharaonic.notification-dependencies.all_modelsfalse
pharaonic.notification-dependencies.delete_on_soft_deletetrue

#Publish Tags

TagPublishes
notification-dependencies-configconfig/pharaonic/notification-dependencies.php
notification-dependencies-migrationsThe migration, to database/migrations
laravel-notification-dependenciesThis package's configuration and migration
pharaonic-configThe configuration, shared tag with other Pharaonic packages
pharaonic-migrationsThe migration, shared tag with other Pharaonic packages
pharaonicConfiguration and migration, shared tag with other Pharaonic packages

#Exceptions

ExceptionWhen
InvalidArgumentExceptionA dependency doesn't use HasDependentNotifications (and all_models is off), is unsaved, has no integer or non-empty string key, or has a key or morph type longer than 191 characters.
InvalidArgumentExceptiondependencies() returns something other than an array of Eloquent models.
InvalidArgumentExceptionThe notification was stored on another connection than the default one of DatabaseNotification.
RuntimeExceptionThe database channel didn't return the stored notification, so it can't be linked.
RuntimeExceptionThe migration runs without the notifications table, or over an existing dependency table.

#How It Works

You don't need this section to use the package, but it explains the guarantees and the limits.

#Linking

Laravel's database channel stores the notification and returns the row, then dispatches NotificationSent. A synchronous listener links that row to its dependencies:

  1. It inserts one link per dependency, inside a transaction (a savepoint when one is already open).
  2. It reads every dependency row with a lock (FOR UPDATE) to check that it still exists.
  3. When one is gone, the new notification is deleted.

Because the links are inserted before the lock, a deletion at the same moment either commits first, so the check sees the dependency gone, or waits for the links to commit, so its cleanup sees them. The check is a plain table query, so a global scope (a tenant or visibility scope) can't make an existing dependency look deleted.

When linking fails for any other reason, the notification and its links are deleted and the exception is rethrown, so a notification is never left with incomplete links.

#Deleting

Deleting a model with HasDependentNotifications (or any model, with all_models) runs one indexed lookup on (dependable_type, dependable_id). When notifications depend on it, they are deleted with set-based queries in batches of 500 ids, without loading them. The links are deleted explicitly and through the foreign key.

#Why a Separate Table

A subject_type and subject_id pair on notifications would allow only one dependency per notification. A separate table lets one notification depend on several models of different types, and leaves Laravel's notifications table untouched.

#Limitations

  • Bulk deletes are not seen. Query builder deletes, raw SQL, and database cascades fire no Eloquent events, so their notifications stay.
  • The database channel only, on the default connection of DatabaseNotification. A notifiable with its own connection stores its notifications where the cleanup would never find them, so such a notification with dependencies is deleted and InvalidArgumentException is thrown.
  • One identity per type and key. A link stores the morph type and key only, so the same model class on two connections (a database per tenant, for example) shares links: deleting Order #1 on one connection deletes the notifications of Order #1 on the other. Dependencies on another connection work otherwise, but the cleanup is not atomic across connections, and the lock only covers dependencies on the notifications connection.
  • A short window. The notification row exists for a moment before its links do. A notification read in that window, or a dependency deleted on another connection at the same time, is not covered by a transaction across both.
  • SQLite enforces the cascade only with foreign_key_constraints enabled on the connection. The package deletes links explicitly when it deletes notifications itself.
Laravel 7

Laravel 7 no longer receives security fixes upstream. Plan an upgrade to a supported Laravel line.

#Examples

#1. Comments on an Order

A customer is notified of each new comment on their order. The notification goes away when the comment is deleted, or when the whole order is.

app/Order.php
1namespace App;
2 
3use Illuminate\Database\Eloquent\Model;
4use Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependentNotifications;
5 
6class Order extends Model
7{
8 use HasDependentNotifications;
9 
10 public function customer()
11 {
12 return $this->belongsTo(User::class, 'user_id');
13 }
14 
15 public function comments()
16 {
17 return $this->hasMany(Comment::class);
18 }
19}
app/Comment.php
1namespace App;
2 
3use Illuminate\Database\Eloquent\Model;
4use Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependentNotifications;
5 
6class Comment extends Model
7{
8 use HasDependentNotifications;
9}
app/Notifications/OrderCommented.php
1namespace App\Notifications;
2 
3use App\Comment;
4use App\Order;
5use Illuminate\Notifications\Notification;
6use Pharaonic\Laravel\NotificationDependencies\Concerns\HasDependencies;
7 
8class OrderCommented extends Notification
9{
10 use HasDependencies;
11 
12 protected $order;
13 
14 protected $comment;
15 
16 public function __construct(Order $order, Comment $comment)
17 {
18 $this->order = $order;
19 $this->comment = $comment;
20 }
21 
22 public function dependencies(): array
23 {
24 return [$this->order, $this->comment];
25 }
26 
27 public function via($notifiable)
28 {
29 return ['database', 'mail'];
30 }
31 
32 public function toDatabase($notifiable)
33 {
34 return [
35 'order_id' => $this->order->id,
36 'comment_id' => $this->comment->id,
37 ];
38 }
39 
40 // toMail()...
41}
app/Http/Controllers/OrderCommentController.php
1namespace App\Http\Controllers;
2 
3use App\Comment;
4use App\Notifications\OrderCommented;
5use App\Order;
6use Illuminate\Http\Request;
7 
8class OrderCommentController extends Controller
9{
10 public function store(Request $request, Order $order)
11 {
12 $comment = $order->comments()->create($request->validate([
13 'body' => 'required|string',
14 ]));
15 
16 $order->customer->notify(new OrderCommented($order, $comment));
17 
18 return back();
19 }
20 
21 public function destroy(Order $order, Comment $comment)
22 {
23 $comment->delete(); // the customer's notification is deleted too
24 
25 return back();
26 }
27}

The mail is sent once and stays in the inbox; only the stored database notification is removed.

#2. A Friend Request That Is Answered

A pending friend request is deleted once it is accepted or declined. Its notification goes with it, so the recipient never sees a request they can no longer act on. FriendRequest uses HasDependentNotifications.

app/Http/Controllers/FriendRequestController.php
1use App\FriendRequest;
2use App\Notifications\FriendRequestReceived;
3 
4public function store(User $recipient)
5{
6 $request = FriendRequest::create([
7 'sender_id' => auth()->id(),
8 'recipient_id' => $recipient->id,
9 ]);
10 
11 $recipient->notify((new FriendRequestReceived)->dependsOn($request));
12}
13 
14public function accept(FriendRequest $request)
15{
16 auth()->user()->friends()->attach($request->sender_id);
17 
18 $request->delete(); // removes the "new friend request" notification
19}

#3. Unread Notifications per Project

Show, on a project list, how many unread notifications each project still has, using the relation from HasDependentNotifications.

app/Http/Controllers/ProjectController.php
1use App\Project;
2 
3public function index()
4{
5 $projects = Project::withCount(['dependentNotifications as unread_count' => function ($query) {
6 $query->whereNull('read_at')
7 ->where('notifiable_id', auth()->id())
8 ->where('notifiable_type', auth()->user()->getMorphClass());
9 }])->get();
10 
11 return view('projects.index', compact('projects'));
12}
resources/views/projects/index.blade.php
1@foreach ($projects as $project)
2 <a href="{{ route('projects.show', $project) }}">
3 {{ $project->name }}
4 @if ($project->unread_count)
5 <span class="badge">{{ $project->unread_count }}</span>
6 @endif
7 </a>
8@endforeach

#4. A Model You Can't Edit

A notification depends on a model from another package, so you can't add the trait to it. Turn on all_models, and query its links through NotificationDependency:

config/pharaonic/notification-dependencies.php
1'all_models' => true,
1use Pharaonic\Laravel\NotificationDependencies\Models\NotificationDependency;
2 
3$user->notify((new SubscriptionRenewed)->dependsOn($subscription));
4 
5NotificationDependency::whereDependable($subscription)->count(); // 1
6 
7$subscription->delete(); // the notification is deleted

#5. Keep Notifications of Archived Records

Tasks are soft deleted when archived and can be restored. Keep their notifications until a task is permanently removed:

config/pharaonic/notification-dependencies.php
1'delete_on_soft_delete' => false,
1$assignee->notify((new TaskAssigned)->dependsOn($task));
2 
3$task->delete(); // archived: the notification stays
4$task->restore(); // back, with its notification
5$task->forceDelete(); // gone for good, and so is the notification

#Troubleshooting

#"A notification can only depend on a model that uses HasDependentNotifications"

The model you passed to dependsOn(), or returned from dependencies(), doesn't use the trait. Add HasDependentNotifications to the model, or set all_models to true when you can't edit it.

#"A notification cannot depend on an unsaved model"

The model hasn't been saved yet, so it has no key to link. Save it before you call dependsOn():

1$comment = Comment::create($data); // not new Comment($data)
2 
3$user->notify((new OrderCommented)->dependsOn($comment));

#The migration fails with "needs the [notifications] table first"

Laravel's notifications table doesn't exist yet. Create it, then migrate again:

Terminal
php artisan notifications:table
php artisan migrate

#The migration fails with "table already exists"

Another table already uses the name notification_dependencies. Publish the configuration, set table to another name, and migrate again.

#Notifications stay after a bulk delete

Model::where(...)->delete(), raw SQL, and database cascades fire no Eloquent events. Delete the models one by one:

1Order::where('status', 'cancelled')->get()->each->delete();

#Notifications stay after a soft delete

delete_on_soft_delete is false, so only forceDelete() removes them. Set it to true if a soft delete should count.

#Notifications stay after adding a morph map

Links store the morph type at the moment the notification is sent. When you add or change a morph map alias later, older links still hold the previous type and no longer match the model. Update dependable_type in the dependency table to the new alias when you change the morph map.

#The notification disappears right after it is sent

One of its dependencies no longer existed when the notification was stored, so it was deleted. This happens with queued notifications whose dependency was deleted while the job waited, and with dependencies that are already soft deleted while delete_on_soft_delete is true.

#"The notification was stored on the [...] connection"

The notifiable stores its notifications on a connection other than the default one of DatabaseNotification. The package only links and purges notifications on that default connection, so it deletes the notification and throws. Store notifications with dependencies on the default connection.

SQLite enforces the cascading foreign key only when foreign_key_constraints is enabled on the connection. Enable it in config/database.php:

config/database.php
1'sqlite' => [
2 // ...
3 'foreign_key_constraints' => true,
4],

The package already deletes the links explicitly whenever it deletes notifications itself.

Contributors

Amazing people who made this package possible

This package wouldn't exist without the contributions of our amazing community. A huge thank you to everyone who has helped improve Notification Dependencies!

Moamen Eltouny

@MoamenEltouny

48 contributions

Want to Contribute?

We welcome contributions from everyone! Whether it's bug fixes, new features, or documentation improvements - every contribution counts.