1. Go to this page and download the library: Download stateflow/engine library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
stateflow / engine example snippets
// ❌ Without StateFlow - scattered, error-prone
if ($order->status === 'pending' && $action === 'pay') {
$order->status = 'paid';
// Hope you remembered to send the email...
// Hope you remembered to log it...
// Hope no other code sets status to something invalid...
}
// ✅ With StateFlow - clean, auditable, testable
$machine->apply($order, 'pay');
use Stateflow\Engine\StateMachine;
$machine = StateMachine::create(OrderStateDefinition::class);
// Your subject (from your database, or anywhere)
$order = Order::find(1); // $order->status is 'pending'
// Apply a transition
$machine->apply($order, 'pay'); // $order->status is now 'paid'
// Save to your database
$order->save();
public const STATES = ['draft', 'pending', 'approved', 'rejected'];
public const TRANSITIONS = [
'submit' => [
'from' => ['draft'], // Can only submit from draft
'to' => 'pending',
],
'approve' => [
'from' => ['pending'], // Can only approve from pending
'to' => 'approved',
],
'reject' => [
'from' => ['pending', 'approved'], // Can reject from multiple states
'to' => 'rejected',
],
];
// All of these work:
$machine->apply($order, 'pay'); // Object
$machine->apply($data, 'pay'); // Array
$machine->apply($eloquentModel, 'pay'); // Eloquent
$machine = StateMachine::create(
definition: OrderStateDefinition::class, // Required
workflow: OrderWorkflow::class, // Optional: guards and hooks
accessor: new CustomAccessor(), // Optional: custom state access
field: 'status', // Optional: default is 'status'
initialState: 'pending', // Optional: for initialize()
);
// Apply a transition (throws if invalid)
$machine->apply($subject, 'pay');
// Check if transition is possible
if ($machine->can($subject, 'pay')) {
$machine->apply($subject, 'pay');
}
// Get current state
$state = $machine->getState($subject); // 'pending'
// Get available transitions from current state
$transitions = $machine->getAvailableTransitions($subject); // ['pay', 'cancel']
$machine = StateMachine::create(
definition: OrderStateDefinition::class,
initialState: 'pending',
);
$order = new Order(); // status is null
$machine->initialize($order); // status is now 'pending'
// Get all states
$machine->getStates(); // ['pending', 'paid', 'shipped', 'cancelled']
// Get all transitions
$machine->getTransitions(); // ['pay' => [...], 'ship' => [...], ...]
// Export as array (JSON-serializable)
$machine->toArray();
// Export as Mermaid diagram
echo $machine->toMermaid();
// Validate without creating (returns errors array)
$errors = StateMachine::validate(OrderStateDefinition::class);
if (empty($errors)) {
echo "Definition is valid!";
}
use Stateflow\Engine\Attribute\Guard;
final class OrderWorkflow
{
#[Guard('pay')]
public function hasValidAmount(
object|array $subject,
string $transition,
string $from,
string $to
): bool {
return $subject->amount > 0;
}
#[Guard('ship')]
public function hasShippingAddress(
object|array $subject,
string $transition,
string $from,
string $to
): bool {
return $subject->shipping_address !== null;
}
}
#[Guard('pay')]
public function hasValidAmount($subject, ...): bool {
return $subject->amount > 0;
}
#[Guard('pay')]
public function hasCustomer($subject, ...): bool {
return $subject->customer_id !== null;
}
// Both must return true for 'pay' to be allowed
use Stateflow\Engine\Attribute\Guard;
use Stateflow\Engine\Attribute\Before;
use Stateflow\Engine\Attribute\OnExit;
use Stateflow\Engine\Attribute\OnEnter;
use Stateflow\Engine\Attribute\After;
final class OrderWorkflow
{
#[Guard('pay')]
public function hasValidAmount($subject, $transition, $from, $to): bool
{
return $subject->amount > 0;
}
#[Before('pay')]
public function logPaymentAttempt($subject, $transition, $from, $to): void
{
Log::info("Attempting payment for order {$subject->id}");
}
#[OnExit('pending')]
public function onLeavePending($subject, $transition, $from, $to): void
{
Log::info("Order {$subject->id} is no longer pending");
}
#[OnEnter('paid')]
public function sendConfirmationEmail($subject, $transition, $from, $to): void
{
Mail::send(new OrderConfirmation($subject));
}
#[After('pay')]
public function notifyWarehouse($subject, $transition, $from, $to): void
{
Warehouse::notify($subject);
}
}
function(
object|array $subject, // The subject being transitioned
string $transition, // The transition name ('pay')
string $from, // The previous state ('pending')
string $to // The new state ('paid')
): void
$event->subject; // The subject that transitioned
$event->transition; // Transition name ('pay')
$event->from; // Previous state ('pending')
$event->to; // New state ('paid')
$event->occurredAt; // DateTimeImmutable
use Stateflow\Engine\Accessor\StateAccessorInterface;
final class MyCustomAccessor implements StateAccessorInterface
{
public function getState(object|array $subject, string $field): ?string
{
// Your logic
}
public function setState(object|array &$subject, string $field, string $state): void
{
// Your logic
}
}
echo $machine->toMermaid();
// app/StateMachines/OrderStateDefinition.php
final class OrderStateDefinition
{
public const STATES = ['pending', 'paid', 'shipped', 'cancelled'];
public const TRANSITIONS = [
'pay' => ['from' => ['pending'], 'to' => 'paid'],
'ship' => ['from' => ['paid'], 'to' => 'shipped'],
'cancel' => ['from' => ['pending', 'paid'], 'to' => 'cancelled'],
];
}
// app/Models/Order.php
class Order extends Model
{
public function pay(): void
{
$machine = StateMachine::create(OrderStateDefinition::class);
$machine->apply($this, 'pay');
$this->save();
}
}
// Usage
$order = Order::find(1);
$order->pay();
// src/Entity/Order.php
#[ORM\Entity]
class Order
{
#[ORM\Column]
private string $status = 'pending';
public function getStatus(): string
{
return $this->status;
}
public function setStatus(string $status): void
{
$this->status = $status;
}
}
// Usage
$machine = StateMachine::create(OrderStateDefinition::class);
$machine->apply($order, 'pay');
$entityManager->flush();
use Stateflow\Engine\Exception\InvalidTransitionException;
use Stateflow\Engine\Exception\InvalidStateException;
use Stateflow\Engine\Exception\GuardRejectedException;
use Stateflow\Engine\Exception\ValidationException;
use Stateflow\Engine\Exception\StateAccessException;
try {
$machine->apply($order, 'pay');
} catch (InvalidTransitionException $e) {
// Transition doesn't exist or not allowed from current state
} catch (GuardRejectedException $e) {
// A guard blocked the transition
echo "Blocked by: " . $e->guardMethod;
} catch (InvalidStateException $e) {
// Subject has invalid state
}
// Catch all StateFlow exceptions
use Stateflow\Engine\Exception\StateflowExceptionInterface;
try {
$machine->apply($order, 'pay');
} catch (StateflowExceptionInterface $e) {
// Any StateFlow error
}