If you want your transitions to do more than just changing the state, you can use transition classes.
Imagine transitioning a payment's state from pending to failed, which will also save an error message to the database.
Here's what such a basic transition class might look like.
class PendingToFailed extends Transition
private Payment $payment;
private string $message;
public function __construct(Payment $payment, string $message)
$this->payment = $payment;
$this->message = $message;
public function handle(): Payment
$this->payment->state = new Failed($this->payment);
$this->payment->failed_at = now();
$this->payment->error_message = $this->message;
Now the transition should be configured in the model:
abstract class PaymentState extends State
public static function config(): StateConfig
->allowTransition(Pending::class, Failed::class, PendingToFailed::class);
It can be used like so:
$payment->state->transitionTo(Failed::class, 'error message');
State::transitionTo method will take as many additional arguments as you'd like,
these arguments will be passed to the transition's constructor.
The first argument in the transition's constructor will always be the model that the transition is performed on.
Another way of handling transitions is by working directly with the transition classes, this allows for better IDE autocompletion, which can be useful to some people. Instead of using
transitionTo(), you can use the
transition() and pass it a transition class directly.
$payment->state->transition(new CreatedToFailed($payment, 'error message'));
If you're using the approach above, and want to ensure that this transition can only be performed when the payment is in the
Created state, you may implement the
canTransition() method on the transition class itself.
class CreatedToFailed extends Transition
public function canTransition(): bool
If the check in
canTransition() fails, a
\Spatie\ModelStates\Exceptions\TransitionNotAllowed will be thrown.