Strict writes and drift
The model attribute is the source of truth. With strict_writes on, saving a changed lifecycle attribute throws DirectStateWriteException:
| Write path | Covered | Outcome |
|---|---|---|
$m->status = …; $m->save(), update([...]), forceFill([...])->save(), push(), increment('x', 1, ['status' => …]) | yes | throws (or adopted inside allowDirectWrites) |
Model::create([...]), factories ->create() / ->state([...]) | creation | any declared state accepted; initial history row |
factory afterCreating / seeder changing a saved model’s state | yes | throws: wrap in Lifecycles::allowDirectWrites() or seed with LIFECYCLE_STRICT_WRITES=false |
saveQuietly(), withoutEvents(), Model::query()->update(), relation update(), upsert(), insert(), DB::table(), raw SQL | no (no model event) | drift: adopted at the next mutation, or by lifecycle:adopt |
Lifecycles::allowDirectWrites(fn () => $listing->update(['status' => ListingStatus::Closed])); // adopted at once
Lifecycles::for($listing)->adopt(); // reconcile one subject now
Lifecycles::model(Listing::class)->adopt(chunk: 500); // every row (= php artisan lifecycle:adopt)Adoption writes an adopted history row, cancels the old state’s schedules, schedules the new state’s expiry and fires LifecycleAdopted and LifecycleTransitioned. The time in the adopted state starts at the moment of adoption, because the real entry time is unknown. A NULL stored state (rows from insert(), or a column added to an existing table) is set to the initial state. An adopted row is not reversible, so nothing below it can be undone.
With strict_writes off, a model save that changes the state is adopted immediately — history row, schedules and events. Only writes that bypass model events leave drift behind.
Evolving a definition
History stores raw state values and transition names. Renaming a transition makes its past rows irreversible. Renaming or removing a state needs a data migration — wrapped in Lifecycles::allowDirectWrites(), or followed by php artisan lifecycle:adopt. Run php artisan lifecycle:validate after every change.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.