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 Key Laravel Package

mpociot/laravel-composite-key

Adds support for composite primary keys in Laravel Eloquent models. This package lets models with multi-column keys work with common Eloquent operations like saving, updating, deleting, and finding records, without hacking around the framework's single-key assumptions.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. The package is a single Eloquent trait (~30 lines) that overrides setKeysForSaveQuery() and key-aware find(). There is no service provider, config, migration, or facade — it sits entirely at the model layer and has no footprint anywhere else in the stack. That makes it architecturally cheap to install and architecturally narrow in where it can safely be used: composite-key models must stay at the leaf of the relationship graph (the "many" side of a hasMany with a single-column FK). Making one a belongsTo target, a hasMany parent, or a polymorphic target will produce wrong joins because FK resolution goes through a scalar getKeyName(). The same scalar assumption breaks routing, cache keys, JSON id fields, queue payloads, and any third-party package that builds SQL from getQualifiedKeyName().

Integration feasibility. Low effort, high verification burden. One composer require, one use statement, $primaryKey set to an array, $incrementing = false. No infrastructure, no data migration, no config surface. The cost is not adoption, it is the audit: confirming trait/namespace naming, confirming signature compatibility against your specific Laravel version, confirming the table actually has a composite PRIMARY KEY rather than just a unique index, and proving the override is live via query log before any application code depends on it.

Technical risk. This is the dominant concern and it is not subtle.

  • Abandoned profile. Last release 2016-05-17, 35 stars, zero dependents, single author, MIT. No community validation and no upstream triage. Procurement/security review will classify this as abandoned and will not pass it as a maintained dependency — plan for that outcome rather than discovering it late.
  • Signature drift is the failure mode. Modern Laravel types setKeysForSaveQuery(Builder $query). A 2016 override without the type hint either fatals loudly on declaration compatibility (acceptable) or is silently bypassed by the parent (dangerous). The silent variant yields where 0 = 1 updates that affect zero rows while save() returns truthy — a data-integrity bug, not a crash.
  • Semantic collision. Under this trait, Model::find([1,2]) means "the tuple (1,2)", whereas core means "records 1 and 2". Any generic helper that does $class::find($ids) behaves differently for these models. This is a footgun for shared abstractions.
  • Schema you control should not use this. A surrogate id plus UNIQUE(order_id, line_no) delivers identical integrity guarantees with zero friction across binding, relations, caching, and tooling. The package is justified only for vendor, legacy, or replicated tables whose composite key is a fact of the data.

Key questions to resolve before committing.

  1. Which Laravel and PHP versions must this run on, and who re-validates on each upgrade?
  2. Can we change the schema? If yes, why are we here at all?
  3. How many models genuinely need the trait — is it a handful, or is it spreading?
  4. Is the source system externally owned (ERP, vendor, warehouse mirror) such that tuple identity is non-negotiable?
  5. Which existing packages in composer.json traverse models generically (admin panels, activity logs, authorization drivers, resource tooling)? Each is a potential invalid-SQL site.
  6. Do we adopt, fork-and-vendor, or write ~one engineer-week of in-house trait? The comparison is against in-house work, not against a paid product.
  7. Who owns the fork? If no name can be written down, do not adopt.

Integration Approach

Stack fit. Laravel/Eloquent only, which is where our application lives — so there is no new runtime, no new infrastructure, no new operational surface. The right pattern is to isolate the trait on one shared abstract base model (CompositeKeyModel) rather than importing it into individual models. That makes the eventual swap — to a fork, to fico7489/laravel-composite-key, or to in-house code — a single-line change in one file.

Migration path. There is no data migration; the package is stateless. The relevant migration is the exit path: pin the version, vendor it into packages/ or our own namespace so a composer update cannot silently change or drop it, and keep the use statement in exactly one place. Treat it as owned source, not as a dependency.

Compatibility. Compatibility is gated entirely on the setKeysForSaveQuery signature in the Laravel version we run. The canary is a single integration test covering create → find → update → delete on a real table. That test must be part of the upgrade checklist and must run on every Laravel and PHP bump. Also set $keyType = 'string' for string keys so casting and strict comparisons behave. Do not assume updateOrCreate(), fresh(), or refresh() work — implement find-then-fill explicitly, and test refresh if it appears in a code path.

Sequencing.

  1. Schema decision first. PM-level call: surrogate key + unique composite index for tables we control; composite handling only for tables we don't.
  2. Spike. Install, open a tinker session against a real table, enable the query log, and confirm the WHERE contains both columns and never id or 0 = 1. Stop here if it fails.
  3. Canary test. Land the four-path CRUD test before any feature work.
  4. Blast-radius inventory. Enumerate the models that will use the trait and the third-party packages that touch models generically. This list is the scope of the risk.
  5. Isolate. One base model, one use, one owner named in the ADR.
  6. Rollout by path type. Read paths first (where chains, aggregates, pagination, orderBy — all unaffected by the trait). Write paths second, behind a repository that owns the key tuple and never lets a caller pass a hand-built array.
  7. Handle routing explicitly. Do not rely on implicit route binding; getRouteKey() is scalar by contract. Ship the explicit route closure form (/orders/{orderId}/lines/{lineNo}) — greppable, no contract gymnastics.
  8. Decide fork vs. adopt vs. build as part of the same ADR, not as a follow-up.

Operational Impact

Maintenance. Unmaintained, and that is a planning assumption rather than a risk to monitor. The operational cost is a per-upgrade canary test plus an owner who reads the trait's ~30 lines against the current framework method whenever Laravel moves. If nobody owns that, the package should not be adopted. Budget the fork/vendor work as the realistic steady state, not the exception.

Support. No upstream support, no community, no issue triage, zero dependents to learn from. Escalation is internal by definition. Any security or dependency-review gate that requires a maintained upstream will fail this package — surface that early so the decision is made on engineering merit rather than reversed at release time.

Scaling. Runtime performance is a non-issue: queries filter on two indexed columns, and the trait adds no overhead beyond the WHERE clause shape. The scaling risk is organizational. Every additional model using the trait multiplies the audit surface — routing, caching, serialization, generic helpers, third-party admin tooling. If the count of composite-key models is growing, that is a signal to stop and return to the schema conversation rather than to keep extending the pattern. Prefer query-builder bulk operations (where(...)->delete(), where(...)->update()) over per-model loops; they sidestep the trait entirely and are usually a better fit anyway.

Failure modes, ordered by severity.

  1. Silent no-op writes. Override bypassed; save() returns truthy, zero rows updated. Highest severity because it is invisible and corrupts user trust in the data. Detection: query log in tests, not in production.
  2. Declaration incompatibility fatal. Loud, caught at boot, cheap to fix.
  3. Invalid SQL from generic tooling. Any package constructing where {$model->getQualifiedKeyName()} = ? receives an array and emits malformed SQL. This is the most likely surprise — it appears in the admin surface, not in the code we wrote.
  4. Broken implicit route binding / cache keys / serialized id fields. Predictable if audited, embarrassing if not.
  5. find() semantic collision. Generic helpers passing ID arrays silently misinterpret them as tuples.
  6. Insert breakage if $incrementing is left true — insertGetId() against a table with no single auto-increment column.
  7. fresh() / refresh() unproven — reload explicitly via a where chain if needed.

Ramp-up. Learning cost is roughly an hour for a mid-level Laravel engineer — the package is small and reads like standard Eloquent. The real ramp-up is the audit: enumerating generic model consumers, smoke-testing the admin surface, and writing the canary test. Estimate adoption as small and verification and blast-radius control as the bulk of the effort. Frame it to stakeholders as "one engineer-week of risk work to save one engineer-week of build," and be explicit that the trade is only worth it for a small, named, read-mostly set of externally-keyed tables. For everything else, the correct TPM recommendation is the schema redesign.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package