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

Laravel Composite Relations Laravel Package

reedware/laravel-composite-relations

Adds composite foreign key support to Eloquent relationships. Define Belongs To, Has One, and Has Many relations using multiple columns, with full eager loading and "where has" query support through a simple trait on your base model.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package is a single trait (HasCompositeRelations) applied to the application's base Eloquent model. There is no service provider, facade, config file, or migration — it adds three methods (compositeBelongsTo, compositeHasOne, compositeHasMany) that return genuine Relation subclasses. That means with(), whereHas, doesntHave, whereRelation, and withCount all flow through Laravel's standard existence-query machinery rather than a parallel query path. This is the right shape for the problem: the gap is in Eloquent's relation layer, and the fix is confined to that layer. The deliberate scope limit (three relation types, no morph/through/pivot variants) is a feature for evaluation purposes — the surface area we'd be betting on is small and readable in an afternoon.

Integration feasibility. Two steps: composer require reedware/laravel-composite-relations, then use HasCompositeRelations; on the abstract base model. Effort is trivial; blast radius is not. Because it lands on the base class, every model in the application inherits the capability and the version constraint. This should be treated as a reviewed, single-owner dependency decision rather than a routine composer update.

Technical risk. Ranked:

  1. Default glue is 'or'. Composite relations default to ("fk1" = ? or "fk2" = ?), which matches any row sharing either column. This is a silent over-match that produces plausible-looking wrong data — the worst failure class. 'and' must be passed explicitly on every relation and enforced by code review, a helper wrapper, and a test asserting row identity against fixtures that share one key column and differ on the other.
  2. Argument asymmetry. compositeBelongsTo($related, $foreign, $local, $relation = null, $glue = 'or') — the fourth parameter is the relation name, not the glue. compositeHasOne/compositeHasMany take ($related, $foreign, $local, $glue = 'or'). Passing 'and' as the fourth argument to compositeBelongsTo silently names the relation and. This is a first-hour mistake every implementer will make once.
  3. Version lockstep with Laravel majors. The release history is a chain of major bumps tied to Laravel EOL (v4 dropped L8/L9 and PHP 7.2/8.0; v5 dropped L10 and PHP 8.1; v6 dropped L11). Every Laravel upgrade forces a package major bump and a re-verification pass. This is scheduled work, not incidental.
  4. Adoption signal is weak. 22 stars, 0 dependents. That is effectively zero third-party validation — no evidence of the package surviving diverse production schemas. Assume we are the primary integration test.
  5. Bus factor of one. Single maintainer, GitHub-issues-only support, no LTS branch.

The mitigating factor for 4 and 5: the package is small enough to vendor or fork. Budget for that as the fallback rather than treating it as an emergency.

Key questions.

  • Do we actually need composite keys, or can we add surrogate primary keys and use native single-key relations? This is the cheapest possible resolution and should be evaluated first for every candidate table.
  • What is the enumerated list of genuine composite foreign keys in our schema? It should be short and written down before install.
  • Who owns the Laravel-major upgrade path for this dependency, and is it in the upgrade runbook?
  • Do we need reedware/laravel-relation-joins for reporting surfaces, or does with() cover it? Every added dependency adds a version matrix.
  • Are withCount and whereRelation verified against seeded fixtures, or assumed?

Integration Approach

Stack fit. Laravel 12.x/13.x on PHP 8.2–8.5+ for package v6. Older Laravel versions require older package majors — pin deliberately and confirm the matrix row matches our actual runtime, not our intended one.

Migration path. This is a capability adoption, not a data migration. Nothing changes in the schema, and no existing relation breaks by adding the trait. That said, adopt incrementally rather than installing and immediately rewriting queries: the risk here is correctness of individual relations, not the install itself.

Compatibility. Low conflict surface because there is no service provider and no container binding. The one theoretical conflict is another package that also overrides relation methods on the base model — check the base class before and after. The optional companion (laravel-relation-joins) carries its own version matrix and should be evaluated as a separate dependency decision, not bundled in.

Sequencing.

  1. Schema audit — enumerate every composite FK and confirm none can be replaced with a surrogate key. Write the list down.
  2. Install and add the trait to the base model in a dedicated PR, with the version constraint pinned.
  3. Implement one relation with fully explicit key arrays and 'and' glue — no $primaryKeys convention shortcuts yet.
  4. Assert toSql() shows (a = ? and b = ?) and write a test seeding two parents that share one key column and differ on the other. If the SQL shows or, stop.
  5. Add a base-model helper that hard-codes 'and' and correct argument position per relation type, so implementers never pass the glue by hand.
  6. Expand to remaining relations; only then consider the $primaryKeys convention to drop boilerplate arrays.
  7. Add the package to the Laravel upgrade runbook as a known-blocking dependency.

Operational Impact

Maintenance. The package tracks Laravel's support policy rather than offering an LTS line. Each Laravel major triggers a package major, which means this dependency gates our upgrades until a compatible release lands. Given the maintainer's single-person cadence, assume we may need to publish a fork patch on our own timeline for any non-trivial upgrade. Because the shipped surface is essentially one trait, that patch is cheap — budget it as a known contingency, not a crisis.

Support. No vendor, no SLA. GitHub issues on a 22-star repo. Internal expertise required: at least one engineer must read the trait source, not just the README. The README is good but does not adequately flag the 'or' default — §4 is buried and reads as an advanced option when it is in fact the primary correctness lever.

Scaling. All risk is at the query layer, and it is index-shaped. Composite joins need composite indexes on both sides of the pair or eager loading degrades to table scans; verify with EXPLAIN on the largest candidate table before committing to a dashboard or report built on the relation. with() eliminates N+1 as usual. The 'or' glue failure mode is worse than slow — it inflates result sets and will silently corrupt aggregate reports (withCount, revenue rollups, dedupe counts). Prioritize the SQL-assertion test on any relation feeding a number that a human reads.

Failure modes to monitor.

  • Silent over-match from 'or' glue — highest severity, lowest detection probability.
  • Relation silently named and from the compositeBelongsTo fourth-argument trap.
  • Column-pair order mismatch (positional, not name-matched) — pairs wrong columns without erroring.
  • Missing composite index — performance cliff on eager loads as tables grow.
  • withCount / whereRelation edge cases — unverified paths in a low-adoption package.
  • Laravel major upgrade blocked pending a package release.

Ramp-up. Low for a competent Laravel engineer — the API mirrors native Eloquent closely and the trait is short. Realistic estimate: half a day to first working relation including the test and SQL verification. The cost is not learning the API; it is institutionalizing the 'and' rule and the argument-position rule so that the sixth engineer to write a composite relation doesn't reintroduce the over-match. Encode both in a helper and a review checklist, and this package is a reasonable bet; leave them to convention, and it becomes a recurring data-integrity incident stream.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
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