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.
Architecture fit
$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.Integration feasibility
composer require, add trait, annotate properties). No infrastructure, no queue worker, no config file expected.Technical risk
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.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.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.deleted_at, updated_at, state-machine status, tenant re-assignment — must never be annotated. This is a footgun that only shows up in production.Key questions
saving override, or explicit trait method — and which of saveQuietly, withoutEvents, update(), upsert() does it actually cover?save() on a model with ~30 attributes?firstOrCreate/updateOrCreate, and soft deletes?CHECK, or restricted grants — and does the package document it?Stack fit
composer.json constraints against our supported matrix, since we likely run mixed versions across services and PHP 8 attribute support is the hard floor.updateOrCreate).Migration path
#[Immutable] to a column that some code path already rewrites converts a silent data bug into an immediate 500 or a failing job queue.saveQuietly, soft deletes. Document the actual bypass surface empirically rather than from the README.Compatibility
upsert-based sync jobs.Sequencing
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.saving plus isDirty() checks is on the order of 50 lines). Budget for that option in the estimate rather than treating adoption as free.Maintenance
Support
Scaling
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
->update() succeeding quietly, leaving two systems with different beliefs about whether a column is immutable.originalIsEquivalent false negative fails open — silent and unsafe.Ramp-up
How can I help you explore Laravel packages today?