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 Morph Map Generator Laravel Package

spatie/laravel-morph-map-generator

Stop forgetting to register models in your Laravel morph map. This package auto-generates morph maps at boot, letting each model register itself via a simple getMorphClass method. Includes configurable autogeneration and a base directory setting for non-default model locations.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. This is a narrow, single-purpose package: a service provider that scans a configured set of directories for Eloquent models, calls getMorphClass() on each, and registers the resulting alias→class pairs with Laravel's morph map. It sits at the framework boot layer, not in the domain layer, so it does not constrain our application architecture. The real architectural commitment is not the package — it is the convention it enforces: every persisted polymorphic model must declare a stable, explicit alias. That convention is individually valuable regardless of whether we adopt this package, which makes the package optional-to-swap (we could drop it and hand-write the map with a CI test to keep it honest). That is a favorable risk profile for a utility of this size.

Integration feasibility. Low-to-moderate. Mechanically it is a composer require, a publishable config, and one method on a base model. The work is in the codebase audit, not the install:

  • Audit every model that appears in a *_type column today and record its current stored value.
  • Introduce an abstract base model (or interface) whose getMorphClass() throws by default, so omissions fail loudly rather than silently resolving to a FQCN.
  • Reconcile with any existing Relation::enforceMorphMap() calls — both write to the same registry, so ordering and precedence must be verified, not assumed.
  • Handle models outside the scan path (package-provided models, auth models in non-default namespaces), which must still be registered manually.

Technical risk. Moderate, concentrated in two places rather than spread across the integration:

  1. Cache staleness is the primary failure mode. With autogenerate on, the map is rebuilt at boot; in production it is expected to come from morph-map:cache. If a deploy ships a new or renamed polymorphic model without regenerating the cache, the map is stale and polymorphic relations resolve incorrectly or throw — a data-integrity-shaped bug, not a crash-shaped one. This must be bolted into the deploy pipeline, not left to developer discipline. Laravel 11+ optimize/optimize:clear hooks exist (added in 1.3.0) but only help if our pipeline actually calls them.
  2. Resolver stability. The custom-resolver feature (1.2.0) is the most seductive and the most dangerous capability here. Any resolver output that can change — a renamed table, a refactored namespace — silently invalidates every existing *_type row. The README warns about this explicitly. As TPM I would gate resolver usage behind an ADR and require a documented backfill plan; the default of explicit per-model getMorphClass() literals is far safer because the diff is reviewable in the PR.

Secondary risks: boot-time reflection cost if autogenerate is left enabled in production (measurable, usually small, entirely avoidable); alias collisions between two models returning the same string, which I do not see explicitly guarded in the documented surface and should be verified with a test; and the fact that getMorphClass() throwing during boot fails the entire application, which is the right behavior for CI and a sharp edge for a bad hotfix deploy.

Key questions to resolve before a decision:

  • What is our current morph map state — enforced, absent, or partial? How many *_type rows exist across how many tables?
  • Can we guarantee morph-map:cache runs on every deploy, including hotfix and rollback paths? What is the rollback story if the cached map and the DB disagree?
  • Are there package or vendor models in polymorphic columns that the scanner will never see, and who owns keeping those entries current?
  • Do we accept "app boot fails if any model omits getMorphClass()" as a deliberate guardrail, and does our on-call rotation understand that failure signature?
  • Is 77 stars / 0 dependents acceptable for a boot-layer dependency? (Spatie's maintenance record across Laravel 9→13 is the mitigating factor; the absence of downstream users means we are early adopters and should expect to find the edges ourselves.)

Verdict: assess/trial, do not standardize. Pilot on one service with an existing polymorphic relation, prove the cache-invalidation path end to end, and keep the manual-map fallback documented as the exit.

Integration Approach

Stack fit. Laravel 9 through 13, PHP 8.1 through 8.4, per the changelog trail (1.0.7 → 1.3.1 → 1.4.0/1.4.1). This aligns with a modern Laravel estate and, notably, the package has kept pace with framework majors rather than lagging — the Laravel 13 support line landing in 1.4.0 and the class-loading-error fix in 1.4.1 are both signals of active upkeep. It requires no queue worker, no scheduler, no external service, and no schema change of its own. That means it can be scoped to a single application without any shared-infrastructure work.

Two fit notes worth flagging: the release dates in the changelog read ahead of the current ecosystem, so verify the version we pin actually resolves against our Laravel constraint in a scratch install before committing to it in a plan; and the package is opinionated about models living under a single base_directory, which fits a conventional app/ layout and needs explicit config for domain-oriented layouts (src/Domain).

Migration path. Phased, low-blast-radius, and reversible at every step:

  1. Audit (no code change). Enumerate models in polymorphic relations and the distinct values currently in every *_type column. This is the artifact that determines whether the migration is a half-day or a quarter — if existing values are already short aliases, the work is convention-only; if they are FQCNs, there is a data backfill.
  2. Convention layer. Add the base model with a throwing getMorphClass() and implement explicit literal aliases matching the existing stored values. Verify with the audit from step 1 that the generated map is byte-for-byte equivalent to today's behavior. This is the checkpoint that makes the rest safe.
  3. Shadow mode. Install the package, publish config, enable autogenerate in local and staging only. Diff the generated map against the enforced map. Any difference is a bug in step 2, not in the package.
  4. Manual entries. Register out-of-path models — package models, non-App namespaced models — via standard Relation::enforceMorphMap() and confirm precedence with the generated map.
  5. Production cutover. Enable caching, wire morph-map:cache into the deploy script as a hard gate, and wire morph-map:clear into the pre-deploy/rollback path.
  6. Backfill (conditional). Only if step 2 could not preserve existing values. This is a separate, reviewed data migration with a rollback; it is not part of the package rollout and should be planned as its own ticket.

Compatibility. The main compatibility surface is not Laravel versioning — it is our own codebase's existing relationship to the morph map. Specifically: any pre-existing enforceMorphMap call, any test that asserts on a FQCN in a *_type column, any factory or seeder that writes a raw class string, and any queue payload or serialized model reference that embeds a morph class. The last one is easy to miss and only fails on in-flight jobs during the deploy window — sequence the cutover accordingly (drain or accept a short queue quiesce).

Sequencing recommendation. Steps 1–2 are pure upside and should proceed independently of the package decision, because explicit aliases are good practice on their own. Steps 3–5 constitute the actual package adoption and should be a single tracked change with a named owner. Step 6 is a separate change with its own risk review. Do not collapse 4 and 6.

Operational Impact

Maintenance. Low and largely externalized. The package's own code surface is small, and the maintenance history is a steady cadence of framework-compatibility bumps rather than feature churn — the pattern you want in a dependency you rarely think about. Our ongoing obligation is the convention itself: every new polymorphic model must declare an alias, and the throwing base model converts that from a code-review hope into a build failure. That is the right trade — a small, permanent tax on model authors in exchange for eliminating the "forgot to add it to the morph map" class of production bug.

Support. Community-supported, MIT, no SLA. Spatie's responsiveness is historically good but contractual guarantees are zero, and with 0 dependents we have no peer cohort to lean on when something breaks. Mitigation: pin the version, vendor the fallback (a hand-written map plus a CI test asserting it matches the discovered models), and treat any upgrade as a normal dependency change requiring the same map-diff check as the initial rollout. For a boot-layer component this is acceptable because the exit is cheap; it would not be acceptable for something holding state.

Scaling. Not a scaling concern in the conventional sense — the morph map is a static in-memory registry, and once cached there is no per-request or per-entity cost. The only scaling-adjacent item is the number of discovered models: with autogenerate on, boot cost grows with reflection over the model directory. This is fine in development and should be eliminated in production by caching. If a service has hundreds of models, measure boot time before and after in staging rather than assuming.

Failure modes, ranked by what actually costs us:

  • Stale cached map after a deploy — new model missing or alias changed, polymorphic relations misresolve. Silent, data-shaped, hardest to detect. Detected by: a post-deploy smoke assertion that the cached map hash matches a freshly generated one.
  • Alias collision — two models resolve to the same string, one silently wins. Detected by: a CI test asserting map cardinality (unique values, unique keys).
  • Boot failure from an unimplemented getMorphClass() — the whole app fails to start. Loud, cheap to diagnose once the on-call runbook names it, but a genuine hotfix footgun; the throwing base model is the right guardrail only if the failure signature is documented.
  • Divergent resolver output — only if we use a custom resolver. Avoided by not using one, or by treating resolver changes as schema changes requiring a backfill.
  • Class-loading errors during scan — addressed upstream in 1.4.1; still worth a staging verification against our autoload configuration.

Ramp-up. A senior Laravel engineer can complete the install and config in under a day; the audit and convention layer are where the time goes — budget roughly 1–2 engineering days for a mid-sized application with no existing morph map, more if step 6 backfill is required. Team-wide ramp is effectively zero: contributors do not need to know the package exists, only that their model needs a getMorphClass() literal, and the base model teaches that at the moment they hit it. On-call ramp is the one real gap — the runbook must cover the stale-cache and boot-failure signatures before production cutover, or the first incident will be diagnosed from scratch.

Bottom line for the operating model: this package reduces a recurring class of defects for a small, bounded, reversible cost. Its risk is not technical complexity but the discipline of the deploy pipeline — specifically the guarantee that the cached map is never older than the code. If that guarantee can be made mechanical, adopt it on one service and revisit; if it would rely on developer memory, defer, because a hand-written map with a CI test delivers most of the benefit with none of the boot-layer coupling.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib