Skip to content

Controller Component ​

The Workflow.Workflow component provides controller helpers for handling workflow transitions with standardized flash messages.

Setup ​

Load the component in your controller:

php
public function initialize(): void
{
    parent::initialize();

    $this->loadComponent('Workflow.Workflow');
}

Configuration ​

php
$this->loadComponent('Workflow.Workflow', [
    'messages' => [
        'success' => "Transition '{transition}' applied successfully.",
        'blocked' => 'Transition blocked: {reasons}',
        'error' => 'Transition failed: {error}',
    ],
    'flashKey' => null, // Use custom flash key if needed
]);

Saving changes ​

The component calls the behavior's applyTransition() method. With the default autoSave => false, a successful result changes the entity in memory. For the form examples below, enable autoSave when adding the behavior to your table:

php
$this->addBehavior('Workflow.Workflow', [
    'workflow' => 'order',
    'autoSave' => true,
]);

Keep the behavior's other configuration. For a persisted transition with logging, locking and a transaction in one call, use the behavior's transition() method shown in Getting started.

Basic Usage ​

Apply a transition with flash messages ​

php
public function transition(int $id): Response
{
    $this->request->allowMethod(['post']);

    $order = $this->Orders->get($id);
    $transition = $this->request->getData('transition');

    $this->Workflow->applyTransition(
        $this->Orders,
        $order,
        $transition,
        ['reason' => $this->request->getData('reason')],
    );

    return $this->redirect(['action' => 'view', $id]);
}

Handle transition from form data ​

For the common pattern of extracting transition name and reason from request data:

php
public function transition(int $id): Response
{
    $this->request->allowMethod(['post']);

    $order = $this->Orders->get($id);

    return $this->Workflow->handleTransition(
        $this->Orders,
        $order,
        ['action' => 'view', $id],
    );
}

This extracts transition and reason from the request data automatically.

If the request does not include a transition, handleTransition() now fails early with a flash error and redirects without attempting any workflow change. You can override that message per call:

php
return $this->Workflow->handleTransition(
    $this->Orders,
    $order,
    ['action' => 'view', $id],
    ['missingTransitionMessage' => 'Please choose a transition first.'],
);

Custom Messages ​

Override messages per-call:

php
$this->Workflow->applyTransition($this->Orders, $order, 'ship', [], [
    'messages' => [
        'success' => 'Order has been shipped!',
        'blocked' => 'Cannot ship: {reasons}',
    ],
]);

Working with the Result ​

The applyTransition() method returns the TransitionResult, allowing additional logic:

php
$result = $this->Workflow->applyTransition($this->Orders, $order, 'pay');

if ($result->isSuccess()) {
    // Send confirmation email, trigger webhooks, etc.
    $this->Orders->sendPaymentConfirmation($order);
}

return $this->redirect(['action' => 'view', $id]);

Form Example ​

Typical form for triggering a transition:

php
<?= $this->Form->create(null, ['url' => ['action' => 'transition', $order->id]]) ?>
<?= $this->Form->hidden('transition', ['value' => 'ship']) ?>
<?= $this->Form->control('reason', [
    'type' => 'textarea',
    'label' => 'Reason (optional)',
]) ?>
<?= $this->Form->button('Ship Order', ['class' => 'btn btn-primary']) ?>
<?= $this->Form->end() ?>

Without the Component ​

If you prefer more control, use the workflow directly:

php
$orders = $this->fetchTable('Orders');
$result = $orders->getBehavior('Workflow')->transition($order, 'pay', [
    'reason' => 'Payment captured',
]);

if ($result->isSuccess()) {
    $this->Flash->success('Payment recorded.');
} elseif ($result->isBlocked()) {
    $this->Flash->warning('Blocked: ' . implode(', ', $result->getBlockedBy()));
} else {
    $this->Flash->error('Error: ' . $result->getError()?->getMessage());
}

Released under the MIT License.