fenos/notifynder
Notifynder is a Laravel package for database-backed user notifications. It makes it easy to send notifications, retrieve and count unread items, mark them as read, and integrate with Eloquent models to build alerts and activity feeds.
Version 4.3.0 is an additive minor release (#280 add eager loading) — no breaking changes, no migration or config republish required. Upgrading is a composer update away, but it's worth pinning ^4.3 so the new eager-loading API is available consistently across environments.
First steps:
composer.json in the tagged release for the supported PHP/Laravel range before bumping — the eager-loading work often rides on newer Eloquent APIs (withOnly, Model::preventLazyLoading), and the package may have quietly dropped older Laravel versions in the same window.#280 is the authoritative spec for how relations are declared and resolved. The test fixtures are the fastest way to see the intended call signature — faster than the README, which may lag a minor release by a few days.relations/with option on the facade, resource, or config block. That single seam is where nearly all of your day-to-day usage will live.DB::listen for repeated select * from ... where id = ?). Add the relation(s) via the new option, then re-measure the query count.A good smoke test before touching production code:
DB::listen(fn ($q) => logger()->debug($q->sql, $q->bindings));
$results = YourPackage::query()->with(['author', 'comments.user'])->get();
// expect the query count to stay flat as $results->count() grows
Declarative eager loading at the call site. Pass relations alongside your existing filters rather than reaching into the model layer. This keeps the load strategy visible where the query is written, which matters when different consumers of the same resource need different graphs (an API index vs. a detail view vs. an export).
$rows = Model::query()
->with(['owner', 'tags'])
->paginate(50);
Default to $with only for genuinely ubiquitous relations. If a relation is needed on ~every read of the model, protected $with = ['owner']; removes a whole class of N+1 bugs. Be conservative — anything loaded by default is loaded in queued jobs, console commands, and test factories too, and that cost compounds silently.
Nested graphs with dot notation, but budget for them. Each nesting level is an additional query. with(['comments.user.profile']) is three queries regardless of row count — cheap compared to N+1, but not free. For deep graphs on large pages, prefer splitting into two queries or loading the outer relation and letting the inner one lazy-load behind a cached accessor.
Pair with aggregate counts instead of loading full relations. When you only need "how many", withCount is dramatically cheaper than loading the collection and calling ->count().
Guard against regressions in tests, not in production. Turn on strict lazy-loading in your non-production environments so a future refactor that drops the with() fails loudly:
// AppServiceProvider::boot()
Model::preventLazyLoading(! app()->isProduction());
Then assert query counts on the endpoints that motivated the upgrade — this is the regression test that actually protects the 4.3.0 win.
Compose with pagination and chunking. The payoff scales with result set size, which means the eager-loaded path pairs naturally with paginate(), cursorPaginate(), and chunkById() in exports and scheduled jobs. Verify each of these paths separately; chunked iteration re-runs the eager load per chunk, so relation count matters more there than on a single page.
Constrain eager loads where the package supports it. Filtering a relation (e.g. only published comments) inside the eager-load closure avoids loading rows you'll discard in the view — the most common way an eager-loaded endpoint ends up slower in memory than the N+1 version it replaced.
select('id', 'title'), you must include the foreign key (user_id) or the eager load resolves to null with no error. This is the single most common cause of "the eager loading doesn't work" reports.withCount and with interact. Both add subqueries or joins; combining several on a wide table can produce a surprising query plan. Check EXPLAIN on your largest table before shipping a page that uses both.withOnly on collections you've already loaded, or constrain the relation.with() on the hot path, or version your cache keys on write.DB::listen, Laravel Telescope's query tab, and Model::preventLazyLoading() cover almost every case. If the relation still lazy-loads despite the with(), check for a $with override on a child model or a global scope adding its own constraints.belongsToMany joins and ordering inside constrained eager loads. Read 4.3.1+ notes before pinning an exact version.Bottom line for 4.3.0: adopt it for the N+1 fix, not for new surface area. The upgrade is low-risk, the payoff concentrates in index/listing endpoints and exports, and the durable value comes from the lazy-loading guard you add alongside it.
How can I help you explore Laravel packages today?