---
title: "Handlers, hooks, stamps and snapshots — Lifecycle | Roundly"
description: "Run side effects inside the transaction with handlers and onEnter/onExit hooks, stamp timestamps, snapshot attributes and compensate on rollback."
url: https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel/handlers-and-hooks
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Lifecycle for Laravel](https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel.md)

# Handlers, hooks, stamps and snapshots

A transition handler runs inside the transaction after the state write; implement CompensatesTransition to make it reversible. Stamps set columns to “now” on entry, and snapshots capture attributes so a rollback can restore them:

```php
use RoundlyConsulting\Lifecycle\Contracts\CompensatesTransition;
use RoundlyConsulting\Lifecycle\Contracts\TransitionHandler;
use RoundlyConsulting\Lifecycle\DataTransferObjects\RollbackContext;
use RoundlyConsulting\Lifecycle\DataTransferObjects\TransitionContext;

final readonly class ReserveStock implements TransitionHandler, CompensatesTransition
{
    public function __construct(private Inventory $inventory) {}

    public function handle(TransitionContext $context): void
    {
        $this->inventory->reserve($context->subject);
    }

    public function compensate(RollbackContext $context): void
    {
        $this->inventory->release($context->subject);
    }
}

$lifecycle->state(OrderStatus::Paid)->stamps('paid_at')->onEnter(SendReceipt::class);
$lifecycle->transition('fulfil')
    ->from(OrderStatus::Paid)->to(OrderStatus::Fulfilled)
    ->handledBy(ReserveStock::class)
    ->snapshots('stock_note')
    ->reversible(within: '1 hour');
```

## The transaction

In one transaction on the subject’s connection, apply() does the following:

- 1\. Locks the subject row.
- 2\. Re-checks the transition.
- 3\. Dispatches LifecycleTransitioning.
- 4\. Writes the state with a compare-and-swap.
- 5\. Runs onExit hooks, the handler and onEnter hooks.
- 6\. Saves the model when it is dirty.
- 7\. Appends the history row.
- 8\. Updates the state record and the schedules.

Events then fire after commit. If anything throws, everything rolls back, including the in-memory model. Keep external side effects (emails, HTTP calls) in listeners of the after-commit events: a deadlock retry re-runs handlers.

## Rules

- Handlers, guards and hooks given as class names are resolved from the container on every run, so constructor injection and container overrides work. Instances and closures work too.
- A handler’s model changes are saved after it; its exceptions roll everything back.
- A handler or hook may not change the lifecycle attribute being written: InvalidLifecycleUsageException::dirtyStateAttribute, and the transition rolls back — strict writes on or off. The same holds for a compensating handler during a rollback. Every other model, and every other lifecycle of the same model, keeps strict writes inside handlers.
- On entry into a quota’d state, a handler or hook that changes a quota scope column throws InvalidLifecycleUsageException::quotaScopeChanged.
- Without CompensatesTransition (or reversible(withoutCompensation: true)), a transition with a handler cannot be rolled back — lifecycle:validate warns irreversible\_by\_handler. A closure handler cannot compensate.
- Self-transitions run the handler but no hooks, because no state is left or entered. Initialization, adoption and rollbacks run no hooks; initialization and adoption set no stamps.
- onEnter / onExit hooks implement StateHook (\_\_invoke(StateHookContext $context)) and receive the subject, lifecycle, state and the TransitionContext.
- Stamps are always snapshotted, so a rollback restores them.

## 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](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

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

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel.md)
