modera/notification-bundle
Modera Notification Bundle adds a flexible notification system to Modera CMS applications, letting you compose, queue, and deliver messages through channels like email and SMS, with reusable templates, recipient handling, and simple integration into Laravel-based projects.
Read this first: this is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no auto-discovery, and no config/notifications.php to publish. It ships Doctrine ORM entities and Symfony DI service definitions from the Symfony 2.8/3.x era (last release 2017-12-12). Before you invest time, ask whether you actually need it — Laravel already ships a database notification channel (Notifiable trait + notifications table + DatabaseNotification model) that covers ~90% of what this bundle does, with no bridge code.
If you are evaluating it for a Symfony app (or porting it into Laravel):
composer require modera/notification-bundle — then immediately read composer.json for the php, symfony/*, and doctrine/orm constraints. If the app runs PHP 8.1+/Symfony 6, expect conflicts; you'll be running it with --ignore-platform-reqs at your own risk.AppKernel::registerBundles() (Symfony ≤4). It depends on modera/foundation-bundle, so that gets registered too.doctrine:schema:update --dump-sql and convert the resulting notification table DDL into a migration (Laravel) or Doctrine migration. Do not rely on schema:update in production.Entity/ — the notification model and its fields (recipient, type/level, message, timestamps, read flag).Service/ — the manager. Modera bundles use their own "serviceable" pattern (AbstractService::getDef() returning a ServiceDefinition), not plain Symfony service YAML, so don't hunt for the class name in services.yml.DependencyInjection/Configuration.php — the handful of knobs (user class/interface, table prefix, allowed types).Resources/views/ — if there's a default rendering partial, copy it rather than override it.Inject a thin wrapper, not the manager directly. Modera's AbstractService retrieval API is awkward ($container->get('modera_notification.notification_manager')->get($container) in older versions). Wrap it in your own NotificationService with notify(User $u, string $type, array $payload) so the rest of the app never touches Modera types. This also makes the eventual port/removal a one-file change.
Fan-out is the main design decision. For a single recipient, create one row per event. For "notify all 5,000 users," do not loop-save synchronously — chunk the inserts (->persist() in batches of 500, then flush()/clear()) or push the fan-out onto a queue job with an idempotency key. The 2017-era entity almost certainly has no bulk-insert helper.
Write notifications inside the business transaction, or after commit. Persisting a notification in the same unit of work as the thing it describes is simplest, but if the outer transaction rolls back your users get phantom notifications in other code paths. Pick one rule and apply it consistently — the safer default is "create after the business transaction commits."
Read/unread needs an explicit query, not a flag check. Use a repository method like findForRecipient($user, $onlyUnread, $limit, $offset) ordered by createdAt DESC, and expose two counters: unread badge count and paginated list. Never load all notifications and filter in PHP — the badge count renders on every page.
Type is a string, labels are yours. Store a stable machine value (export.ready, invoice.paid) plus a small JSON payload column if available; map type → label/icon/URL in a PHP enum-like class and render in a Blade/Twig partial. Store data, never rendered HTML — otherwise translations, escaping, and route changes all break retroactively.
Add a retention job. A nightly command that deletes read notifications older than N days (and unread older than, say, 90). This table grows faster than anything else in the app and nobody remembers to clean it.
If you're bridging into Laravel: don't. Copy the schema idea (recipient id, type, payload, read_at, created_at; index on (recipient_id, read_at, created_at)), implement it as a Laravel Notifiable database notification or a plain Eloquent model with a Notification::markRead() scope. You get queues, broadcasting, and mail channels for free.
ServiceDefinition, and the container id differs from the class name. If $container->has('modera_notification.notification_manager') returns false, check Resources/config/services.xml and the bundle's compiler pass rather than assuming the bundle didn't register.flush() and confirm with the Doctrine SQL logger (doctrine.dbal.logging) that an INSERT actually ran.User model doesn't match, Doctrine throws a mapping/association error at metadata-load time, not at runtime. Resolve this before writing any feature code.app/Resources/ModeraNotificationBundle/views/) → override the entity class (doctrine.orm.resolve_target_entities / bundle override) as a last resort, since entity overrides break on every schema diff.NotificationService interface survives, and you map old rows into the new table with a single SQL INSERT ... SELECT (type → type, read flag → read_at, message/payload → JSON column).How can I help you explore Laravel packages today?