State machine and workflow engine for CakePHP with PHP 8 Attributes, YAML/NEON config support, and admin UI.
- PHP 8.2+
- CakePHP 5.2+
composer require dereuromark/cakephp-workflowLoad the plugin:
bin/cake plugin load WorkflowRun migrations:
bin/cake migrations migrate --plugin WorkflowConfigure the plugin in your config/app.php:
'Workflow' => [
'loader' => [
'namespaces' => [
'App\\Workflow',
],
'configPath' => CONFIG . 'workflows' . DS,
],
'logging' => true,
'locking' => true,
'timeouts' => true,
'lockDuration' => 30,
],Create state classes in your namespace:
<?php
namespace App\Workflow\Order;
use Workflow\Attribute\StateMachine;
use Workflow\State\AbstractState;
#[StateMachine(name: 'order', table: 'Orders', field: 'state')]
abstract class OrderState extends AbstractState
{
}<?php
namespace App\Workflow\Order;
use Workflow\Attribute\Command;
use Workflow\Attribute\FinalState;
use Workflow\Attribute\Guard;
use Workflow\Attribute\InitialState;
use Workflow\Attribute\Transition;
#[InitialState]
#[Transition(to: PaidState::class, name: 'pay', happy: true)]
class PendingState extends OrderState
{
#[Guard('pay')]
public function ensurePayable(): bool|string
{
return (float)$this->getEntity()?->get('total') > 0
? true
: 'Order total must be positive';
}
#[Command('pay')]
public function markPaymentCaptured(): void
{
$this->getEntity()?->set('payment_captured', true);
}
}<?php
namespace App\Workflow\Order;
use Workflow\Attribute\FinalState;
use Workflow\Attribute\OnEnter;
#[FinalState]
class PaidState extends OrderState
{
#[OnEnter]
public function sendReceipt(): void
{
// Runs after the entity enters the paid state.
}
}Install the optional parser you want:
- NEON:
composer require nette/neon - YAML:
composer require symfony/yaml
Create workflow files in config/workflows/:
order:
table: Orders
field: state
states:
pending:
initial: true
paid:
color: '#00AA00'
completed:
final: true
transitions:
pay:
from: [pending]
to: paid
happy: true
complete:
from: [paid]
to: completedAdd the behavior to your table:
public function initialize(array $config): void
{
$this->addBehavior('Workflow.Workflow', [
'workflow' => 'order',
]);
}Apply transitions:
$behavior = $this->Orders->getBehavior('Workflow');
if ($behavior->canTransition($order, 'pay')) {
// Atomic: applies transition, saves entity, logs - all in one transaction
$result = $behavior->transition($order, 'pay', ['user_id' => $userId]);
}See the documentation for the full API.
bin/cake workflow init order Orders # Scaffold new workflow
bin/cake workflow list # List all workflows
bin/cake workflow show order # Show workflow details
bin/cake workflow validate # Validate definitions- PHP 8 Attributes or NEON/YAML definitions
- Guards, commands, and lifecycle callbacks
- Audit logging with user tracking
- Pessimistic locking for concurrent transitions
- Automatic timeouts
- Admin UI with Mermaid.js diagrams
- CLI tools for management and validation
Full documentation: https://dereuromark.github.io/cakephp-workflow/