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

Eloquent Has Many Deep Laravel Package

staudenmeir/eloquent-has-many-deep

Extended HasManyThrough for Laravel 5.5+ lets you define relationships with unlimited intermediate models, supporting many-to-many, polymorphic, and combined relationship types. Define deep relationships by concatenating existing ones or specifying keys manually; includes third-party package support...

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package is additive rather than invasive: it is a trait (HasRelationships) plus relationship classes (HasManyDeep, HasOneDeep) that extend Eloquent's relationship contract. There is no service provider, no config file, no migration, no schema change, and no query-layer replacement. It slots into the existing Eloquent relationship graph, which means it inherits the framework's conventions for eager loading (with()), constraints (whereHas), serialization, and soft deletes. For any application already modeling data as normalized relational tables with Eloquent models — the dominant Laravel architecture — this is a near-zero-friction fit.

Integration feasibility. Effort is small and bounded: one Composer dependency, one trait per participating model, one relationship method per traversal path. Two definition modes lower the bar further — concatenating existing relationships (hasManyDeepFromRelations) is declarative and reuses relationships already reviewed by the team, while the manual mode (hasManyDeep(Related::class, [IntermediateA, IntermediateB], $foreignKeys, $localKeys)) covers paths where those relationships don't exist. The feature surface is unusually complete for a utility package: many-to-many, all four polymorphic variants, BelongsTo hops, composite keys, pivot/intermediate data access, table aliases, and soft-delete handling. Quality signals are strong: CI, Codecov, PHPStan level 10, and a maintained version matrix mapping Laravel 5.5→13 to specific package majors. That matrix is also the primary compatibility contract — it tells us precisely which Laravel upgrade requires which package bump.

Technical risk. The risk is concentrated in generated SQL, not in integration:

  • Query shape. Each additional hop adds joins/subqueries. Deep chains on large tables can produce expensive plans, and whereHas over a deep relationship is a correlated subquery that can be substantially slower than an equivalent hand-written join.
  • Cardinality. Multi-hop many-to-many fan-out produces duplicate parent rows unless distinct()/unique handling is applied. This is a correctness-adjacent footgun that shows up as pagination count drift, not an exception.
  • Key mapping. The manual API is positional arrays. A transposed foreign/local key (especially on the "right side" of a pivot, where the docs explicitly call out the swap) yields silently wrong results rather than an error. This is the highest-probability defect class and needs test coverage, not code review alone.
  • Database portability. Polymorphic and composite-key joins rely on generated SQL that must behave consistently across MySQL, Postgres, SQL Server, and SQLite. Worth verifying against the exact engine and version in production.
  • Version coupling and maintainership. The package must ship a release for each new Laravel major before we can upgrade. With a single-maintainer project, our Laravel upgrade calendar becomes partially dependent on their release cadence. MIT licensing limits the downside (we can fork or vendor), but it is a scheduling dependency to acknowledge.

Key questions.

  1. Which read paths actually need N-level traversal, and what are the row counts at each hop today and at 12/24 months?
  2. Are we on a Laravel version currently covered by a released package major, and what is our annual Laravel upgrade cadence?
  3. Is the use case read-only relationship querying, or do we also need write semantics (attach/sync across hops)? The package is a querying construct; write orchestration remains ours.
  4. Do we have query-plan review capability (DBA or EXPLAIN discipline) for the endpoints we adopt this on?
  5. Is a single-maintainer dependency acceptable for this surface area, or do we require a vendored fork with an internal owner?
  6. What is the acceptable p95 latency for the affected screens, and is a denormalized read model a cheaper answer?

Integration Approach

Stack fit. Any PHP/Laravel service on MySQL, Postgres, SQL Server, or SQLite, using Composer and Eloquent. It composes with adjacent packages in the same author's ecosystem (eloquent-json-relations, laravel-adjacency-list) and with korridor/laravel-has-many-merged and topclaudy/compoships — relevant only if we already use those. If our data access is largely raw query builder or repository-pattern with hand-written SQL, the value proposition drops sharply.

Migration path. Greenfield usage is a non-event. Brownfield, the realistic path is replacing three recurring patterns: (a) hand-rolled multi-join DB::table(...) queries embedded in controllers or repositories, (b) chains of whereHas over intermediate models to express "descendants of X", and (c) ad-hoc ID-plucking queries (Model::whereIn('id', $ids)->get()) that produce N+1-adjacent code. Each replacement is independently shippable and reversible — the package adds capability without removing the old code path, so a per-endpoint cutover with a feature flag is straightforward. Database views are the main incumbent; they remain a valid fallback for the heaviest reporting queries, and we should not force a migration where a view already performs acceptably.

Compatibility. Pin the package major to the Laravel major (e.g., Laravel 13 → 1.22) and encode that in composer.json constraints so a framework bump cannot silently outrun the package. Verify PHP version support against our runtime. Check for local Eloquent extensions — custom Builder subclasses, global scopes on intermediate models, or tenancy packages that rewrite connections — since those interact with the generated queries. Confirm behavior with our soft-delete conventions (models using SoftDeletes in the intermediate path) and with any table-prefix configuration.

Sequencing.

  1. Spike (1–2 dev-days). One reporting endpoint, one relationship, both definition modes compared, EXPLAIN output captured on production-shaped data.
  2. Baseline. Record current query count, latency, and result set for the endpoint before changing anything.
  3. Pilot. Ship the single relationship behind existing tests plus new assertions on row counts and duplicate handling; add distinct/unique-result handling where fan-out exists.
  4. Decision gate. If pilot latency and query count are at or better than baseline, expand to the next 2–3 paths; if not, keep the manual query and stop — the package is not mandatory infrastructure.
  5. Conventions. Publish a short ADR: which mode we default to, how we name these methods, that every intermediate foreign key must be indexed, and that deep relationships are eager-loaded, never lazy-loaded in loops.
  6. CI guard. Add a query-count assertion or Laravel query log test around the affected endpoints to catch regression from future relationship changes.

Operational Impact

Maintenance. Ongoing cost is close to the floor for a third-party dependency: no runtime services, no data to migrate, no configuration to drift. The real maintenance obligation is upgrade choreography — tracking the Laravel-to-package version matrix at each framework upgrade, and confirming a compatible release exists before committing to a Laravel major. Budget roughly an hour per Laravel upgrade cycle to verify, plus whatever remediation is needed if the package lags.

Support. MIT, community-supported, no vendor SLA, no commercial backing. Issue resolution depends on the maintainer's availability; the codebase's PHPStan level 10 and CI/coverage posture suggests changes are reviewable, and the MIT license means an internal fork is a legitimate contingency with a defined owner. For a dependency confined to read-path query construction, that risk profile is acceptable. It would not be acceptable if the package sat on a revenue-critical hot path with no fallback — which argues for keeping the hand-written alternative for the two or three most performance-sensitive queries.

Scaling. This is the dimension that needs active management. Deep relationships scale with the product of the joined tables' selectivity, not with any single index. Practical guardrails:

  • Index every foreign key along the chain — this is the highest-leverage action and is often already satisfied in a well-modeled schema.
  • Always eager-load (with()) deep relationships in list contexts; lazy-loading inside a loop turns one deep query into N.
  • Keep traversal depth to what the domain actually needs; a five-hop chain is a modeling smell worth reviewing.
  • Cap result sets — deep queries returning unbounded collections are where memory pressure appears, especially through with() on a paginated parent.
  • For dashboards and exports over large tables, prefer a denormalized read model or materialized view over an increasingly complex deep relationship. The package is the right tool for correctness and developer ergonomics on moderate data, not a substitute for a reporting layer.

Failure modes. Ranked by likelihood and blast radius:

  1. Silent wrong results from misaligned key arrays — no exception, just incorrect data. Mitigate with fixture-based tests asserting exact result sets on the manual-mode relationships.
  2. Duplicate rows from fan-out — manifests as inflated counts and unstable pagination. Mitigate with unique-result handling and count assertions.
  3. Missing index → slow query — degrades gradually as data grows, so it passes in staging and fails in production. Mitigate with EXPLAIN on production-shaped data at pilot and a latency alert on the endpoint.
  4. Ambiguous column errors — appears when a column name exists in multiple hops of a constraint; the docs call out qualifying columns. Surfaces loudly at query time, so low blast radius.
  5. Soft-delete leakage or over-filtering — depends on our global-scope conventions; surfaces as missing or unexpected rows.
  6. Query size limits — extremely long chains can hit packet/statement-size limits on some engines. Rare; a depth cap prevents it.
  7. Laravel upgrade breakage — package major not yet released for the target framework version. Blocking but visible, and manageable through upgrade sequencing.

Ramp-up. A Laravel-fluent engineer is productive with the concatenation API in under an hour; the manual key-array API realistically takes a day of hands-on work to internalize the ordering and the pivot key-swap rule. Plan for one engineer to own the pattern, author the ADR and the first two relationships, and review subsequent additions — this is a "one person establishes the convention, everyone else copies it" dependency, not a broad training program. Total adoption cost for a focused pilot and three production relationships is on the order of one developer-week, which against the alternative of hand-maintaining multi-join SQL across several endpoints is a favorable trade provided step 4's decision gate is honored.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource