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

Eager Load Pivot Relations Laravel Package

audunru/eager-load-pivot-relations

Eager load relations on pivot models in Laravel Eloquent BelongsToMany relationships. Add the EagerLoadPivotTrait to the related model to load nested pivot data such as units or quantities. Supports Laravel 8 and above.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package is a single-purpose seam, which is the best possible shape for a third-party dependency: one trait (EagerLoadPivotTrait) that overrides newBelongsToMany() on a model and swaps Eloquent's BelongsToMany for a subclass that parses a pivot.* (or custom accessor) segment inside eager-load strings. There is no service provider, facade, config file, migration, or published asset. The pivot model remains a plain Illuminate\Database\Eloquent\Relations\Pivot subclass, so no existing pivot class needs to change. Blast radius is limited to the specific belongsToMany relations that need pivot-relation hydration; every other Eloquent idiom in the codebase is untouched. This is the right architecture for the problem — Eloquent genuinely has no first-class way to eager load relations defined on a pivot model, and the package patches that gap at the narrowest possible point.

Integration feasibility. High and mechanical, if the domain actually stores meaningful relations on the pivot. The realistic trigger case is a pivot carrying a foreign key to a lookup table (the README's plan_item.unit_id → units is the canonical one) that is dereferenced per row in a loop. Without such a case, the package buys nothing. Adoption requires a coordinated three-part change per relation: add the trait to the declaring model, add ->using(SomePivot::class), and expand ->withPivot(...) to include the pivot's foreign-key columns. The third step is the real work — it is easy to miss and fails silently.

Technical risk. Moderate, and concentrated in three areas. First, the mechanism is an override of Model::newBelongsToMany(), an extension point the framework does not contractually guarantee; the changelog references an upstream PR to laravel/framework by the original author, which implies core never adopted this, so the package will always be trailing framework internals. Second, the project's release cadence is a major version per Laravel major (v2→Laravel 10, v3→11, v4→12, v5/v6→13, each with breaking notes and, at v5, a jump to PHP 8.3). That is disciplined maintenance, but it means an upgrade of Laravel is always also a package major upgrade. Third, and most concretely, the README is internally inconsistent: the first example places the trait on Item, while the custom-accessor section states the trait belongs on the "main model" (Plan) — the declaring model is the one that matters. Adopters must verify placement empirically rather than trust the primary example.

Key questions.

  • Do we have a confirmed pivot-relation dereference in a hot path, backed by query-log evidence of N+1? If not, this is a solution seeking a problem.
  • Can we tolerate a single-maintainer dependency whose correctness depends on framework internals, for a performance-only gain?
  • Is every target pivot relation reachable only via ->using() with explicit withPivot() foreign keys, or do some relations use the default generic Pivot?
  • What is our acceptable lag between a Laravel major release and this package's supporting major?
  • Are we willing to own a query-count regression test per affected endpoint, given the failure mode is silent?

Integration Approach

Stack fit. Clean for any Laravel 8–13 application on PHP 8.1+ (8.3+ from v5). No conflict with common ecosystem packages because there is nothing to conflict with — no provider, no global scope, no query macro registration. It composes safely alongside pagination, ->withCount(), and nested eager loading, since it only extends the relation resolution behavior for the pivot segment.

Migration path. Purely additive in both directions. Adding is: composer require audunru/eager-load-pivot-relations, use EagerLoadPivotTrait on the declaring model, ensure ->using() on the relation, extend withPivot(), then change with('items') to with('items.pivot.unit'). Removing is equally cheap: drop the trait, revert the eager-load strings, and replace the pivot dereference with a batched whereIn hydration or a load() on the pivot collection. This low exit cost is the strongest argument for approval — we are not taking on a data-model or schema commitment.

Compatibility. Version-pinning is the primary control. We must pin the package major to the framework major explicitly (composer constraint) and treat Laravel upgrades as requiring a paired package bump and a regression run. The custom accessor feature (->as('planItem') + with('items.planItem.unit')) is supported but adds a second coupled setting that can drift from the relation definition; prefer the default pivot accessor unless a custom name already exists in the codebase.

Sequencing.

  1. Instrument: enable query logging on the target endpoints and record baseline query counts.
  2. Prove the win on one relation in a branch; confirm the query count drops and the JSON shape is unchanged for API consumers.
  3. Standardize the pattern (trait placement, using(), withPivot()) in a short internal note, since the README does not reliably state it.
  4. Roll out relation-by-relation behind normal review, not as a sweeping refactor.
  5. Lock the pattern with query-count assertions in feature tests before enabling it at scale.

Operational Impact

Maintenance. Low ongoing effort, non-trivial upgrade effort. Routine maintenance is near zero — no configuration, no background jobs, no data to migrate. The recurring cost is the framework-upgrade tax: expect a package major with each Laravel major, and budget it inside the Laravel upgrade ticket rather than as a separate item. Note that v5.0.0 and v6.0.0 both landed on 2026-03-24 with identical breaking notes ("Laravel 13 and php 8.3 required") — a duplicated release worth watching but not alarming.

Support. This is the weakest dimension. Nine stars and zero dependents means effectively no external production validation and no community troubleshooting corpus; the de facto documentation is the README plus a 2016 Medium article by the original author. The project is a fork of ajcastro/eager-load-pivot-relations, so the maintenance line depends on a single individual continuing to port the original author's work forward. CI, coverage reporting, semantic-release, and a Trivy security scan are all present and are genuine positive signals of hygiene — but hygiene is not the same as a support contract. Plan for internal ownership of the answer.

Scaling. The performance characteristics are exactly those of native Eloquent eager loading: one additional query per pivot relation level, with IN (...) predicates sized to the parent result set. On large pivot collections this behaves like any other eager load — fine, but watch very wide IN clauses on high-cardinality parents. The critical scaling risk is not the package's behavior but its absence: if the trait or the eager-load string is dropped anywhere, the code silently degrades to per-row queries. That regression appears only under production data volumes, never in a small test fixture.

Failure modes.

  • Silent N+1. Trait omitted, or with('items.pivot.unit') typo'd — no exception, just latency. Primary risk.
  • Silent null relations. unit_id absent from withPivot() — the pivot relation resolves empty with no error.
  • Accessor mismatch. ->as('planItem') present but with('items.pivot...') used — eager load no-ops.
  • Framework drift. Laravel upgraded without the paired package major — hard failure or subtle divergence in relation hydration.
  • Abandonment. No Laravel 14 support on release day. Recoverable at low cost given the trivial exit path, but it will block the framework upgrade until we execute that exit.

Ramp-up. Fast for anyone fluent in Eloquent — the public surface is one trait and one string convention; an experienced Laravel developer is productive in under an hour. The trap is that the documented example contradicts the correct usage, so ramp-up must include the internal note rather than the README alone. Mitigate the silent-failure class by requiring a query-count assertion on every endpoint that adopts the pattern, and by keeping adoption scoped to relations where query-log evidence already demonstrates N+1.

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.
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode