Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Notification Bundle Laravel Package

mgilet/notification-bundle

Symfony bundle that adds a flexible notification system to your app. Attach notifications to any entity via a trait and interface, create and manage them with a service, and display lists and unread counts through built-in controllers and Twig helpers.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it's a Symfony bundle, not a Laravel package. There is no service provider, no config/notifications.php, no facade. composer require mgilet/notification-bundle in a Laravel app gets you a bundle class nothing will ever boot. The 4.0 release (the first in years) fixes bugs and adds config, but it is still a Symfony bundle tied to the Symfony/Doctrine stack. Treat it as prior art and a data model spec, not a Laravel dependency.

If you are maintaining a Symfony app that already uses it:

  1. composer require mgilet/notification-bundle and register Mgilet\NotificationBundle\MgiletNotificationBundle in config/bundles.php.
  2. Run bin/console doctrine:schema:update --dump-sql — the bundle ships three mapped entities: Notification (the message payload), NotifiableEntity (a polymorphic pointer to your user/account), and NotifiableNotification (the join table carrying the seen flag).
  3. Make your User implement Mgilet\NotificationBundle\Model\NotifiableInterface and pull in NotifiableTrait (getIdentifier() / getClass()).
  4. Inject the notifier service (Mgilet\NotificationBundle\Manager\NotificationManager, aliased to NotifierInterface) — confirm the exact id with bin/console debug:container | grep mgilet before wiring it, the alias naming changed across versions.
  5. New in 4.0: if you use a custom Notification entity, no longer use resolve_target_entities. Instead add config/packages/mgilet_notification.yaml with mgilet_notification.notification_class: App\Entity\MyCustomNotification.

First use case: the notification bell. On OrderShipped, create a Notification with a subject like order, a message, and a deep link, then addNotification($user, $notification). Render getUnseenNotificationCount($user) in the nav and getNotifications($user) on a /notifications page, each row exposing seen so you can style unread vs read.

If you're a Laravel developer (likely): don't install it. Copy the shape instead. Laravel's notifications table with notifiable_type/notifiable_id/read_at solves the same problem in one table, and Notifiable + Notification::send() + ->database channel gives you the bell in an afternoon. The one thing this bundle does better is subjects (see below) — worth stealing.

Implementation Patterns

Wrap the notifier, don't scatter it. Inject NotifierInterface into one NotificationService of your own, with intention-revealing methods (notifyOrderShipped, notifyMention). The bundle API is generic and call sites become unreadable fast if you new Notification() inline everywhere.

// Symfony
class NotificationService {
    public function __construct(private NotifierInterface $notifier) {}

    public function orderShipped(User $user, Order $order): void {
        $n = (new Notification())
            ->setSubject('order')          // grouping key
            ->setMessage("Order #{$order->getId()} shipped")
            ->setLink($this->router->generate('order_show', ['id' => $order->getId()]));
        $this->notifier->addNotification($user, $n);
    }
}

Drive it from domain events, not controllers. Symfony EventDispatcher listeners (or Laravel listeners) keep the notifier out of your request path and let you queue the listener later. This is the single highest-leverage integration decision — everything else is CRUD.

Use subject as a category, not a title. It's the one genuinely nice idea in the bundle: addNotification($user, $n, 'order') lets you later mark an entire subject seen in one call, so "clear all order pings" is one operation. Laravel has no equivalent out of the box — emulate it with a type column and a scoped update(['read_at' => now()]).

Three patterns map cleanly to Laravel equivalents:

Bundle Laravel
Notification entity (persisted per delivery) notifications row via toDatabase()
NotifiableEntity (class + identifier) morph columns notifiable_type/notifiable_id
NotifiableNotification.seen read_at timestamp
addNotifications($user, array) Notification::send($user, $collection)
markAllAsSeen($user) $user->unreadNotifications->markAsRead()

Custom notification entity (4.0 pattern). If you need extra columns on the payload, subclass the shipped entity and point the bundle at it via YAML. The 4.0 release makes NotificationManager::createNotification() return your custom class, so factory-style code and instanceof checks finally work.

# config/packages/mgilet_notification.yaml
mgilet_notification:
  notification_class: App\Entity\MyCustomNotification
// App\Entity\MyCustomNotification extends the bundle entity and adds fields
#[ORM\Entity]
class MyCustomNotification extends \Mgilet\NotificationBundle\Entity\Notification
{
    #[ORM\Column(nullable: true)]
    private ?string $category = null;
}

Polling, because there's no push here. The bundle gives you counts, not sockets. Expose a tiny JSON endpoint returning the unread count and poll it every 30–60s with throttling, or use Laravel's broadcast notifications + Echo if you're porting. Don't poll getNotifications() — poll the count and fetch the list lazily when the bell is opened.

Bulk-add in one transaction. addNotifications() exists for fan-out, but the manager flushes per call in some versions; wrap multi-recipient sends in $em->transactional() or you'll issue a query per user.

Gotchas and Tips

  • Compatibility is still the real risk. 4.0 is a genuine new tagged release, but this remains a Symfony-oriented bundle with PHP 7-era signatures; verify the composer.json constraints against your Symfony 6/7 and PHP 8.2+ stack before upgrading. Expect ReturnTypeWillChange and Doctrine dbal 3 issues unless 4.0 widened them. Fork-patch budget still applies.
  • Upgrading custom entities from 3.x: remove resolve_target_entities. 4.0 explicitly says to delete the doctrine resolve_target_entities directive and replace it with mgilet_notification.notification_class. Leaving both in place gives you conflicting mappings or a doctrine:mapping:info duplicate. Run doctrine:schema:validate immediately after.
  • HTML rendering is back on (4.0). Version 3.2 broke HTML in the notification list; 4.0 re-enables it. That means message content is rendered unescaped — sanitize anything user-generated (mentions, comments, order notes) or you have stored XSS. Prefer |raw only on trusted, server-built strings.
  • Notification is a persisted entity, not a value object. Calling addNotification() twice with equivalent content creates two rows. There is no dedupe, no shouldSend() gate, no rate limiting. If you need "one unread per (user, subject, entity)", implement it yourself with a lookup before insert.
  • The seen flag lives on the join row. That's the right design, but it means markAllAsSeen($user) is an UPDATE across NotifiableNotification, and loading a notifiable's notifications pulls two levels of relations — watch for N+1 when rendering a list. Add fetch="EXTRA_LAZY" or a repository query with an explicit join.
  • Orphan rows. Deleting a user leaves NotifiableEntity behind; deleting a notification leaves join rows pointing at nothing. Add onDelete="CASCADE" on the join's foreign keys or a cleanup command. Laravel's morph relation has the same hazard — ->cascadeOnDelete().
  • IDOR on the seen/unseen endpoints. If you use the bundle's shipped controllers/routes, verify they scope the notifiable to $this->getUser(). If they don't, don't route them — write your own controller that takes the notification id and checks ownership before flipping seen.
  • Counts are cheap but called constantly. getUnseenNotificationCount() runs on every nav render. Cache the integer in session/Redis keyed by user id and bust it on write; otherwise it's a COUNT(*) per page view.
  • Extension points worth knowing: override entities via the 4.0 mgilet_notification.notification_class config (no more resolve_target_entities), decorate NotifierInterface to add logging or idempotency without touching the bundle, and override shipped templates under templates/bundles/MgiletNotificationBundle/.
  • Debugging: bin/console debug:container | grep -i mgilet to find the real service id, bin/console debug:config mgilet_notification to confirm your custom class is picked up, doctrine:schema:validate after overriding entities, and doctrine:mapping:info to confirm the join entity isn't mapped twice.
  • Testing: mock NotifierInterface in unit tests and assert on addNotification calls; for integration use SQLite in-memory and doctrine:schema:create in setUp(). Don't test the manager's internals — test that your NotificationService produced the right subject, message, and link. Add one test asserting createNotification() returns your custom class if you rely on the 4.0 fix.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource