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

Product Decisions This Supports

  • Unblocking a data model we don't control. Composite relations matter when record identity is genuinely two columns — partner/ERP feeds, per-vendor summary tables, legacy schemas with no surrogate key, or tenant-scoped natural keys. This isn't a feature that makes a new product possible; it's what lets us ship against a schema we can't redesign without a migration program.
  • Build vs. buy on a narrow, high-skill surface. Eloquent relation internals (eager loading, existence queries, withCount) are easy to get 80% right and expensive to debug at 100%. Buying this for a trait include is materially cheaper than owning custom Relation subclasses, and the cost of being wrong is one composer line.
  • Consolidating ad-hoc reporting joins into the ORM. Today, screens that need composite-key data tend to hand-write joins, which drift from the model layer and hide business rules in controllers. Adopting this lets with() / whereHas() / whereDoesntHave replace those one-off queries — a real maintainability and correctness win for admin/reporting surfaces, and it makes "does this record have a matching source record" answerable in one place.
  • Roadmap scoping for integrations. If a roadmap item depends on ingesting from a system with composite keys, this package determines whether that item is a two-sprint integration or a schema-remapping project. Worth resolving before committing dates.
  • Explicitly not a platform default. This is a precision tool for a short, enumerable list of relations. It does not justify a "we support composite keys everywhere" standard; the decision is scoped to specific tables and recorded as such.

When to Consider This Package

Adopt when all of the following hold:

  • You have a written, short list of composite foreign keys you actually need — realistically one to three. The README's own framing ("single key relationships are typically preferred") should be treated as the default position.
  • The schema is not yours to normalize in the planning horizon — external feed, vendor-owned table, or a migration too large to sequence now.
  • You need Eloquent-native behavior, not just a query: eager loading and existence queries (whereHas, whereDoesntHave, withCount) are the actual requirement. If you only need a join in one report, write the join.
  • Your team will accept the default-glue hazard as a code-review rule. The package defaults to 'or' between composite keys, which silently over-matches rows sharing either column. Mitigable, but it is a standing correctness risk that requires a convention (always pass 'and', encode it in a base-model helper) rather than a config flag.
  • You can live with the parameter asymmetry trap: compositeBelongsTo's fourth argument is the relation name, not the glue, so passing 'and' there silently names the relation and. Again manageable, but it is a footgun a new hire will hit.

Look elsewhere or hold when:

  • You have a single-key option. Adding a surrogate key or a composite unique index plus a lookup is usually the better long-term investment; this package defers normalization, it doesn't remove the need for it.
  • You need relations beyond Belongs To / Has One / Has Many. Has-Many-Through and polymorphic composites are out of scope by design — no roadmap here to wait for.
  • Vendor concentration is a blocker. 22 stars, zero dependents, effectively a single maintainer, no service provider or facade to abstract behind. It is well-run (CI with tests, lint, PHPStan level 9, tracks Laravel's support policy, MIT), but it is a bus-factor-one dependency with no community backstop. That's acceptable for a scoped, low-blast-radius use; it is not acceptable as a hidden dependency of a revenue-critical path.
  • You're on a Laravel version outside the matrix. Support follows Laravel's policy and the last release dropped Laravel 11 (EOL March 2026). Upgrades are forced, not optional — budget for it.
  • The relation-joins integration is the real ask. That's a second dependency with its own version matrix. Only take it if flat reporting rows are a genuine requirement.

How to Pitch It (Stakeholders)

For executives: We occasionally have to work with data tables where a record is identified by two columns rather than one — typically partner feeds or vendor-owned systems we don't control. Laravel's ORM can't express that relationship natively, so today those cases turn into hand-written database queries scattered across the app: slower to build, harder to change, and a source of quiet data bugs. This is a small, MIT-licensed package that closes that gap with a one-line setup, letting those cases use the same standard patterns as the rest of the codebase. The decision is narrow and reversible: it applies to a specific, short list of tables, it carries no licensing cost, and removing it is a one-line revert. The trade-off we're accepting is reliance on a small open-source project — which is why we're scoping it to non-critical paths and documenting it, rather than standardizing on it.

For engineering: Trait on the base model gives every model compositeBelongsTo, compositeHasOne, compositeHasMany, all participating in standard eager loading and existence queries. Three things to internalize before writing a line: (1) the default glue is 'or' and will over-match — always pass 'and', enforced via a base-model helper; (2) compositeBelongsTo's fourth parameter is the relation name, not the glue; (3) key-array order is positional and first-array semantics differ between belongs-to and has-one/has-many. Pair it with reedware/laravel-relation-joins only if we need flat reporting rows. Treat the initial install as a reviewed, single-owner change since it lands on the base class and binds the whole app's version constraint. Add one test per composite relation that seeds two parents sharing one key column and differing on the other — that test is the thing that catches the 'or' mistake in CI rather than in a dashboard.

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