NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Lifecycle for Laravel

Strict writes and drift

The model attribute is the source of truth. With strict_writes on, saving a changed lifecycle attribute throws DirectStateWriteException:

Write pathCoveredOutcome
$m->status = …; $m->save(), update([...]), forceFill([...])->save(), push(), increment('x', 1, ['status' => …])yesthrows (or adopted inside allowDirectWrites)
Model::create([...]), factories ->create() / ->state([...])creationany declared state accepted; initial history row
factory afterCreating / seeder changing a saved model’s stateyesthrows: wrap in Lifecycles::allowDirectWrites() or seed with LIFECYCLE_STRICT_WRITES=false
saveQuietly(), withoutEvents(), Model::query()->update(), relation update(), upsert(), insert(), DB::table(), raw SQLno (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 crypto

By 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.