pindab0ter/constrained-morph-to-for-laravel
Type-constrained polymorphic relationships for Laravel Eloquent. Extend morphTo to accept only specified model types, ensuring type safety and data integrity while keeping the flexibility of a polymorphic database structure.
Feature decisions: whether a polymorphic relation is intentionally open or deliberately closed. Most teams inherit morphTo() relations that were never scoped — a commentable that "any model can attach to" is rarely a product decision, it's an accident of the schema. This package forces the question into the code review: what may attach here, and who owns that list? That is a product question with a one-line answer in the codebase, which is the cheapest possible form of the decision.
Roadmap: retiring a model type without a flag day. The strongest PM-facing use case is narrowing. If you plan to stop allowing Image attachments in favour of Media, you can shrink the allowlist in one release (disallowed types now resolve to null), observe the fallout, backfill and clean data, then delete the class last. That converts a risky migration into a reversible, staged rollout — provided the null result is acceptable during the window (see caveats).
Build vs buy: buy, but budget to own. Reimplementing this is roughly a MorphTo subclass plus a trait and a test matrix against every Eloquent surface you touch — a week or two of senior engineering time plus perpetual maintenance. At 3 stars and zero dependents the package is not a strategic dependency, so the honest framing is "adopt as vendorable code, not as a vendor." Decision: take it, pin the version, accept that a mid-level engineer may own the diff permanently.
Use cases worth scoping in this quarter:
ConstrainedMorphTo<Post|Video, $this>) reduce the "what is this actually?" tax on every engineer who touches the relation. Small, real, compounding.Decisions this package does not support — do not let it be oversold:
null. Any stakeholder claim of "this association can never happen" is false unless you ship a validation rule, observer, or check constraint in the same release.null-on-mismatch is indistinguishable from a deleted parent — a silent failure where you need a loud one.Adopt when most of these hold:
Look elsewhere — or don't adopt — when:
whereMorphedTo() / orWhereMorphedTo(). The v1.3.1 fix shows that surface was broken in a released version; assume the edges are still being found.Before a go decision, close these questions: does the constraint filter at query level or post-hydration (this decides the performance profile of with() / whereHas() on mixed-type sets, and whether disallowed rows leak into eager-loaded collections)? What are the semantics of exists(), associate(), dissociate(), touch(), has(), and cascading deletes when the type is disallowed? What is actual test coverage, including morph maps plus mixed-type eager loading?
To executives — one paragraph: Our database allows any model type to attach to certain shared relations, which was never a deliberate product decision and is now a source of data-hygiene cleanup and, in places, an unquantified risk. This is a small, MIT-licensed, drop-in package that lets us state explicitly which types each relation accepts, with no schema change and no data migration — adoption is one line per relation and reversible by reverting that line. It costs us no licence and roughly a sprint of one engineer's time to adopt safely across a handful of low-risk relations. I recommend adopting it narrowly this quarter to (a) make these allowlists explicit and owned, and (b) give us a staged, reversible path to retiring a model type later. I am explicitly not recommending it for billing, permissions, or audit relations — it filters on read but does not block writes, and a disallowed type resolves to null rather than raising an error, which is the wrong failure mode where correctness is non-negotiable. Because it's a 3-star, single-maintainer package, the plan pins the version and assumes we may own the code ourselves; the diff is small enough that this is a realistic contingency, not a euphemism.
To engineering — the ask:
1.3.1), not a caret range. This is pre-2.0 and still correcting query-builder edge cases; the whereMorphedTo() column-qualification fix is the kind of patch we take deliberately.whereHas, exists(), soft deletes, and morph-map aliases.whereMorphedTo() usage.The one-sentence pitch: A one-line, reversible way to say which models a polymorphic relation may resolve to — worth adopting on low-risk read paths to make implicit rules explicit and unlock staged narrowing, and specifically not a write-side integrity guarantee.
How can I help you explore Laravel packages today?