Transitions
Transition::make('approve')->label('Approve') ->from('submitted')->to('approved') ->by(Actor::slot('approver', default: Actor::resolver('line-manager'))) ->fourEyes('submit') ->guard('order.within-budget') ->effect('order.notify-requester'),Names: start with a lower case letter, then letters and digits (camelCase), up to 40 characters. Unique among the workflows of a resource; the API uses them in paths.
| Method | Meaning |
|---|---|
label() |
text of the button and in the history |
from(...), to() |
states; several starting states are allowed |
by(Actor ...) |
who may run it; one matching actor is enough, see Actors |
guard(key or closure) |
condition on the record; several guards must all pass |
fourEyes('submit', 'creator') |
the actor may not be the actor of these earlier transitions of the record, or its creator |
reason(), reason(required: true) |
asks for an optional or a required reason (at most 2000 characters) |
form([...fields]) |
fields asked when the transition runs |
effect(key or closure) |
runs after the state changed; several effects run in order |
confirm('…') |
confirmation question before running |
optional() |
tenants can switch the transition off |
inbox(false) |
its actors do not get the decision task |
automatic(TaskCondition) |
runs by itself when the tasks of the state meet the condition |
Guards
Section titled “Guards”->guard('order.has-items') // a building block->guard(fn (array|object $order, WorkflowContext $context): bool => $order['amount'] > 0)A transition whose guard fails is not offered, and refused when it runs
(guard). Guards decide by the record, not by the user – that is the job
of actors.
Four eyes
Section titled “Four eyes”->fourEyes('submit') // not the person who submitted->fourEyes('submit', 'creator') // nor the creator of the recordThe current actor may not be the actor of the named earlier transitions of
this record (their last run), or – with creator – the creator. Four
eyes applies to overrides as well. API clients are compared like users.
Reason
Section titled “Reason”reason() asks for an optional reason, reason(required: true) for a
required one. It is stored in the history and in the audit entry. Running
a transition as substitute always requires a reason.
->form([TextInput::make('order_number')->required()->maxLength(30)])->form(static fn (): array => [DateInput::make('delivery_on')->required()]) // built when shown->form('order.delivery') // a building blockThe fields are asked in a dialog, validated like every form and stored
with the transition in the history. All field types work. The
converted values reach the effects as $data. A form built in a closure
can use lang() for its labels.
Effects
Section titled “Effects”->effect('order.send-to-supplier')->effect(fn (array|object $order, array $data, WorkflowContext $context) => service('orders')->send($order, $data['order_number']))Effects run after the state changed and the tasks were updated, in the same transaction: if an effect throws, the transition is rolled back. They receive the record as it is now, the form data and the context. Long running work and calls to other systems belong into a queued job or an outbox started by the effect.
Optional transitions
Section titled “Optional transitions”optional() lets tenants switch the transition off in their
configuration, e.g. cancel for tenants who don’t allow withdrawals.
Switched-off transitions are not offered, don’t count for decisions and
cannot be run by deadlines.
Automatic transitions
Section titled “Automatic transitions”Transition::make('accept')->from('in_review')->to('approved')->automatic(TaskCondition::all('approved')),runs by itself as the system when the tasks of the state meet the condition, see Tasks in parallel.
The context
Section titled “The context”Guards, effects and resolvers receive a WorkflowContext with
workflow, blueprint, resource, tenantId, configuration (the
released configuration of the tenant) and transition.