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 Immutable Attributes Laravel Package

taldres/laravel-immutable-attributes

A Laravel package for defining immutable Eloquent model attributes that can be set once but never changed afterward, helping protect critical values from accidental updates.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit

  • The package targets a real, recurring Eloquent gap: there is no first-class "write-once" column in Laravel. $fillable/$guarded govern mass assignment only, and model events are opt-in per listener. An #[Immutable] property attribute plus a trait is idiomatic PHP 8 / Laravel and requires no schema change, no service provider config, and no migration.
  • Fit is best in domain models with genuine lifecycle invariants: invoice numbers, ledger/external IDs, tenant/account ownership at creation, audit-relevant identifiers, versioned snapshot keys, legally significant timestamps.
  • Fit is poor for anything schema-level. This is an application-layer guardrail, not a database constraint. Any writer that does not hydrate and save an Eloquent model bypasses it entirely.

Integration feasibility

  • Install is trivial (composer require, add trait, annotate properties). No infrastructure, no queue worker, no config file expected.
  • The real integration question is enforcement coverage, not installation. A TPM should treat the package as a coding standard enforcer for the "happy path" and separately decide how the invariant is protected at the data layer.

Technical risk

  • Bypass surface (high): query-builder bulk operations (Model::where(...)->update(), insert(), upsert(), DB::table()), raw SQL, other services/replicas, and Model::withoutEvents() all skip the saving guard. Also confirm behavior under saveQuietly() — if the guard is implemented as a listener rather than an override, it is likely silent.
  • Dirty-semantics edge cases (medium): Eloquent uses originalIsEquivalent() with loose comparison for casted attributes. Float/decimal precision, Carbon date mutation-in-place, array/JSON key ordering, and encrypted casts can produce false negatives (change slips through) or false positives (spurious throw). Each needs a test case before enforcement.
  • Exception behavior (medium): a hard throw inside saving surfaces as a 500 in synchronous requests and as a failed job (with retries) in queues. There is no built-in audit/observe mode mentioned, so a "log-only" rollout likely requires a local fork.
  • Lifecycle collisions (medium): columns that legitimately change post-creation — soft-delete deleted_at, updated_at, state-machine status, tenant re-assignment — must never be annotated. This is a footgun that only shows up in production.
  • Supply chain (high): 0 stars, 0 dependents, single maintainer, MIT. Bus factor of one; abandonment is the base case. Also verify the release metadata — the listed last release (2026-10-15) is future-dated, which suggests either a data-quality issue in the source feed or an untagged/pre-release artifact. Confirm actual tags, CI status, and test coverage on the repo before adoption.
  • Scoring context: an overall score of ~19.9 with opportunity ~57 reads as "real problem, immature solution." That combination usually argues for borrowing the design rather than depending on the artifact.

Key questions

  1. What exactly is the guard mechanism — model boot listener, saving override, or explicit trait method — and which of saveQuietly, withoutEvents, update(), upsert() does it actually cover?
  2. Is there an observe/log-only mode, or is the throw unconditional?
  3. What exception class is thrown, and is it namespaced/distinguishable enough to catch and map to a 409/422?
  4. Which PHP/Laravel versions are supported and tested in CI? Is there a test suite?
  5. How is attribute discovery cached, and what is the added latency per save() on a model with ~30 attributes?
  6. Does it interact correctly with casts (encrypted, JSON, decimal, dates), firstOrCreate/updateOrCreate, and soft deletes?
  7. Is the release we ship actually tagged and reproducible? Is the maintainer responsive to issues/PRs?
  8. What is the intended complement at the DB layer — trigger, CHECK, or restricted grants — and does the package document it?

Integration Approach

Stack fit

  • Assume Laravel 9–12 on PHP 8.1+; verify the composer.json constraints against our supported matrix, since we likely run mixed versions across services and PHP 8 attribute support is the hard floor.
  • It composes cleanly alongside observers, policies, and audit packages. It does not compose cleanly with anything that rewrites model attributes post-creation (state machines, Nova/Filament forms that resubmit all fields, importers using updateOrCreate).

Migration path

  • Do not enable enforcement on existing tables on day one. Adding #[Immutable] to a column that some code path already rewrites converts a silent data bug into an immediate 500 or a failing job queue.
  • Phase 0 — spike: one non-critical model, branch-only, with a full test matrix covering casts, bulk ops, saveQuietly, soft deletes. Document the actual bypass surface empirically rather than from the README.
  • Phase 1 — audit: fork or wrap so violations are logged/alarmed rather than thrown, run for 2–4 weeks, and use the logs to (a) identify legitimate writers to exempt and (b) size the invariant's current violation rate.
  • Phase 2 — enforce on new models and new columns only, where there is no incumbent writer.
  • Phase 3 — enforce on legacy columns, one at a time, gated on a clean audit window.

Compatibility

  • Audit every package and job that writes model attributes: activity/audit loggers, search indexers, CSV/ERP importers, admin panels, and any upsert-based sync jobs.
  • Decide early whether the exception maps to a domain error (409/422 with a clear message) or is allowed to bubble. If it bubbles, the integration is not production-ready for user-facing writes.

Sequencing

  • Parallel workstream: define the data-layer enforcement (trigger or CHECK) for the two or three columns that carry actual legal/financial weight. The package is the developer-ergonomics layer; the constraint is the guarantee. Ship the constraint first if the invariant matters to audit.
  • Because the package is small and single-maintainer, plan to vendor or reimplement (an observer on saving plus isDirty() checks is on the order of 50 lines). Budget for that option in the estimate rather than treating adoption as free.

Operational Impact

Maintenance

  • Assume we own the fork. Pin an exact version, vendor the source if licensing/abandonment risk is unacceptable, and avoid spreading the annotation across dozens of models before the fork decision is made — the migration cost is proportional to annotation footprint.
  • Ongoing cost is low but not zero: every new lifecycle column requires a judgment call, and that judgment needs to live in a written standard, not in tribal knowledge.

Support

  • Expect self-support. No community, no dependents, minimal documentation. The TPM-side mitigation is a short internal runbook: what the exception means, how to distinguish a legitimate new writer from a bug, and who to escalate to.

Scaling

  • Runtime cost is per-save(), not per-query, so volume is proportional to writes, not reads. Reflection on every save would be measurable in bulk-import paths; confirm discovery is cached. Zero infrastructure footprint — no queues, no storage, no external calls.

Failure modes

  • Synchronous request: unhandled exception → 500 with a stack trace leaking model/attribute names. Needs a handler mapping to a domain error.
  • Queued jobs: exception → retry, then dead-letter. A permanently-immutable-write job will retry-to-failure and can mask the real cause; alert on the exception class specifically.
  • Bulk import loops: the throw aborts the batch mid-loop. Rows already written stay written, so imports become partially applied — worse than a clean rejection. Importers need pre-flight validation against the immutability rules.
  • Silent bypass: the most dangerous failure is not the throw, it is ->update() succeeding quietly, leaving two systems with different beliefs about whether a column is immutable.
  • Mis-annotation: an overly broad attribute list fails closed (throws) — noisy but safe. An originalIsEquivalent false negative fails open — silent and unsafe.

Ramp-up

  • Engineer ramp-up is under a day: one trait, one attribute, one exception. The expensive ramp-up is organizational — establishing the rule that the attribute is a guardrail, not a constraint, and that the database is the source of truth. That needs to be explicit in the adoption doc, or teams will over-trust the annotation and skip the constraint work.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource