noerd/notifications
Laravel package for adding notification features to PHP applications. It builds on Laravel's notification system to help you send, store, and manage user alerts, keeping notification logic organized and easy to integrate into existing models and workflows.
Verify the core first. This release requires noerd/noerd ^0.16.35; the previous open constraint would happily resolve to an older core this module cannot run on. Before anything else:
composer show noerd/noerd # must satisfy ^0.16.35
If you are on an older core, upgrade noerd/noerd in the same commit as this module bump — a split upgrade leaves you with a module whose contract methods the core never calls.
Then install and update once, purely as an observation exercise:
composer require <this-package>:^0.1.2
php artisan <module>:install
php artisan <module>:update
Where to look first: the module class itself. The three contract methods are now the source of truth:
getConfigFiles() — what config this module publishespublishModuleExtras() — non-config published assetsensureModuleSetup() — setup entries that must exist in the database/app statehandle() should now be thin. If you find publishing logic or setup writes still living there, that is the first thing to read and understand, because it is the code that will not run on update.
First use case worth trying on day one: run the update command against an installation that predates this release and confirm that setup entries you previously created by hand appear automatically. That single run exercises ensureModuleSetup() on the update path and tells you whether your existing installs are actually self-healing.
Treat the three methods as declarative registration, not code. Each method should return a description of desired end state. The command layer decides when to execute it. Practically:
getConfigFiles() and it is published on both install and update paths. No command edits.publishModuleExtras().ensureModuleSetup(), and it reaches both new and existing installs.Make ensureModuleSetup() idempotent by construction. This is the single most important implementation habit introduced by the release. It runs on install and on every update. Use firstOrCreate / updateOrCreate / explicit existence checks:
protected function ensureModuleSetup(): void
{
Permission::firstOrCreate(['name' => 'manage-widgets']);
Setting::updateOrCreate(
['key' => 'widgets.page_size'],
['value' => config('widgets.default_page_size', 20)]
);
}
Note the choice between firstOrCreate and updateOrCreate — the former is safe for user-edited values, the latter deliberately reasserts a default on every update. Pick per item, not once for the whole method.
Keep handle() a thin orchestrator. As you touch the module, migrate imperative publishing/setup code out of handle() into the declarative methods. If it stays in handle(), it keeps working on fresh installs and silently stops working on updates — the worst kind of divergence to debug.
Test both paths in CI. The bug class this contract exists to prevent is "works on install, missing on update". A useful test matrix is: fresh install → assert config published and setup present; then upgrade-from-previous-version → assert the same. Seeding a database with the older release's state is fiddly, but even a plain "run update twice, assert no duplicate rows and no errors" test catches most regressions.
Pin the core in your own app's composer.json if you ship this module downstream. Pre-1.0 packages move fast, and a ^0.16 style range on a 0.x core is looser than it looks once minor versions carry contract changes.
ensureModuleSetup() runs on every update — non-idempotent code corrupts state. Anything that appends, increments, or creates unconditionally will duplicate on the second update. This is the highest-risk consequence of the release and the first thing to audit in any inherited module code.
Config that is already published will not be refreshed. Laravel's vendor:publish leaves existing files alone. Declaring a file in getConfigFiles() guarantees it gets offered on install and update, not that your local copy changes. When a release alters a config file's defaults, diff it manually or publish with --force in non-production. Never --force in production without a diff.
Anything left in handle() has silently stopped running on update. If a setting or asset goes missing after upgrading, check whether it was moved to the declarative methods but not added to ensureModuleSetup() — moving code out of handle() without registering it in the contract is a silent no-op on the update path.
The core constraint is genuinely breaking, not cosmetic. ^0.16.35 exists because older cores do not know these methods. Do not hand-relax the constraint to get past a resolver conflict; you will get a module that installs and then does nothing. Resolve the conflict by upgrading the core, or hold the module at its previous version.
Debugging the update path. When an update appears to do nothing, confirm it is actually invoking ensureModuleSetup() — add a temporary log line inside it and re-run. If the log never fires, the core is below the required version or the module is resolving to a cached older copy; composer clear-cache and check composer.lock rather than assuming a logic bug.
Extend by overriding, not editing. Subclass the module or override the contract methods in your own module rather than patching vendor files, so you inherit the install/update wiring for free and survive the next minor release. If you need behaviour on both paths, put it in ensureModuleSetup() — there is no separate update hook to reach for.
How can I help you explore Laravel packages today?