Skip to content

Real-World Examples

Complete business workflow examples with multi-step processes.

Order Processing

A shopping cart workflow with calculators, guards, and multi-state transitions.

php
<?php

namespace App\Machines;

use Tarfinlabs\EventMachine\Actor\Machine;
use Tarfinlabs\EventMachine\ContextManager;
use Tarfinlabs\EventMachine\Definition\EventDefinition;
use Tarfinlabs\EventMachine\Definition\MachineDefinition;

class OrderMachine extends Machine
{
    public static function definition(): MachineDefinition
    {
        return MachineDefinition::define(
            config: [
                'initial' => 'cart',
                'context' => [
                    'items' => [],
                    'subtotal' => 0,
                    'tax' => 0,
                    'discount' => 0,
                    'total' => 0,
                    'order_id' => null,
                ],
                'states' => [
                    'cart' => [
                        'on' => [
                            'ADD_ITEM' => ['actions' => 'addItem'],
                            'REMOVE_ITEM' => [
                                'guards' => 'itemExists',
                                'actions' => 'removeItem',
                            ],
                            'CHECKOUT' => [
                                'target' => 'checkout',
                                'guards' => 'hasItems',
                                'calculators' => [
                                    'calculateSubtotal',
                                    'calculateTax',
                                    'calculateTotal',
                                ],
                            ],
                        ],
                    ],
                    'checkout' => [
                        'on' => [
                            'APPLY_DISCOUNT' => [
                                'guards' => 'validDiscount',
                                'calculators' => 'applyDiscount',
                                'actions' => 'recalculateTotal',
                            ],
                            'CONFIRM' => [
                                'target' => 'confirmed',
                                'guards' => 'validTotal',
                                'actions' => 'generateOrderId',
                            ],
                            'BACK_TO_CART' => 'cart',
                        ],
                    ],
                    'confirmed' => [
                        'on' => [
                            'PROCESS_PAYMENT' => [
                                'target' => 'paid',
                                'actions' => 'processPayment',
                            ],
                        ],
                    ],
                    'paid' => ['type' => 'final'],
                ],
            ],
            behavior: [
                'guards' => [
                    'hasItems' => fn(ContextManager $c) => count($c->items) > 0,
                    'itemExists' => fn(ContextManager $c, EventDefinition $e)
                        => isset($c->items[$e->payload['id']]),
                    'validDiscount' => fn(ContextManager $c, EventDefinition $e)
                        => $e->payload['code'] === 'SAVE10',
                    'validTotal' => fn(ContextManager $c) => $c->total > 0,
                ],
                'calculators' => [
                    'calculateSubtotal' => function (ContextManager $c): void {
                        $c->subtotal = array_reduce(
                            $c->items,
                            fn($sum, $item) => $sum + ($item['price'] * $item['quantity']),
                            0
                        );
                    },
                    'calculateTax' => fn(ContextManager $c) => $c->tax = $c->subtotal * 0.1,
                    'calculateTotal' => fn(ContextManager $c)
                        => $c->total = $c->subtotal + $c->tax - $c->discount,
                    'applyDiscount' => fn(ContextManager $c)
                        => $c->discount = $c->subtotal * 0.1,
                ],
                'actions' => [
                    'addItem' => function (ContextManager $c, EventDefinition $e): void {
                        $item = $e->payload['item'];
                        $c->items[$item['id']] = $item;
                    },
                    'removeItem' => function (ContextManager $c, EventDefinition $e): void {
                        unset($c->items[$e->payload['id']]);
                    },
                    'recalculateTotal' => fn(ContextManager $c)
                        => $c->total = $c->subtotal + $c->tax - $c->discount,
                    'generateOrderId' => fn(ContextManager $c)
                        => $c->order_id = 'ORD-' . uniqid(),
                    'processPayment' => fn(ContextManager $c)
                        => $c->set('paid_at', now()->toDateTimeString()),
                ],
            ],
        );
    }
}

Usage

php
$order = OrderMachine::create();

// Add items
$order->send([
    'type' => 'ADD_ITEM',
    'payload' => [
        'item' => ['id' => 'sku-1', 'name' => 'Widget', 'price' => 100, 'quantity' => 2],
    ],
]);

// Checkout - calculators run automatically
$order->send(['type' => 'CHECKOUT']);
expect($order->state->context->subtotal)->toBe(200);
expect($order->state->context->tax)->toBe(20);

// Apply discount and confirm
$order->send(['type' => 'APPLY_DISCOUNT', 'payload' => ['code' => 'SAVE10']]);
$order->send(['type' => 'CONFIRM']);
$order->send(['type' => 'PROCESS_PAYMENT']);

expect($order->state->matches('paid'))->toBeTrue();

Parallel Order Fulfillment

Concurrent processing where payment, shipping, and documents must all complete.

php
<?php

namespace App\Machines;

use Tarfinlabs\EventMachine\Actor\Machine;
use Tarfinlabs\EventMachine\ContextManager;
use Tarfinlabs\EventMachine\Behavior\EventBehavior;
use Tarfinlabs\EventMachine\Definition\MachineDefinition;

class OrderFulfillmentMachine extends Machine
{
    public static function definition(): MachineDefinition
    {
        return MachineDefinition::define(
            config: [
                'id' => 'order_fulfillment',
                'initial' => 'processing',
                'context' => [
                    'order_id' => null,
                    'payment_id' => null,
                    'tracking_number' => null,
                ],
                'states' => [
                    'processing' => [
                        'type' => 'parallel',
                        '@done' => [
                            'target' => 'fulfilled',
                            'actions' => 'sendFulfillmentNotification',
                        ],
                        'on' => [
                            'CANCEL' => [
                                'target' => 'cancelled',
                                'actions' => 'handleCancellation',
                            ],
                        ],
                        'states' => [
                            'payment' => [
                                'initial' => 'validating',
                                'states' => [
                                    'validating' => [
                                        'on' => [
                                            'PAYMENT_SUCCESS' => [
                                                'target' => 'charged',
                                                'actions' => 'recordPayment',
                                            ],
                                            'PAYMENT_FAILED' => 'failed',
                                        ],
                                    ],
                                    'charged' => ['type' => 'final'],
                                    'failed' => [
                                        'on' => ['RETRY_PAYMENT' => 'validating'],
                                    ],
                                ],
                            ],
                            'shipping' => [
                                'initial' => 'picking',
                                'states' => [
                                    'picking' => [
                                        'on' => ['PICKED' => 'packing'],
                                    ],
                                    'packing' => [
                                        'on' => ['PACKED' => 'ready_to_ship'],
                                    ],
                                    'ready_to_ship' => [
                                        'on' => [
                                            'SHIPPED' => [
                                                'target' => 'shipped',
                                                'actions' => 'recordTrackingNumber',
                                            ],
                                        ],
                                    ],
                                    'shipped' => ['type' => 'final'],
                                ],
                            ],
                            'documents' => [
                                'initial' => 'generating',
                                'states' => [
                                    'generating' => [
                                        'on' => ['DOCS_READY' => 'ready'],
                                    ],
                                    'ready' => ['type' => 'final'],
                                ],
                            ],
                        ],
                    ],
                    'fulfilled' => ['type' => 'final'],
                    'cancelled' => ['type' => 'final'],
                ],
            ],
            behavior: [
                'actions' => [
                    'recordPayment' => fn(ContextManager $c, EventBehavior $e)
                        => $c->set('payment_id', $e->payload['payment_id']),
                    'recordTrackingNumber' => fn(ContextManager $c, EventBehavior $e)
                        => $c->set('tracking_number', $e->payload['tracking_number']),
                    'sendFulfillmentNotification' => fn(ContextManager $c)
                        => NotificationService::sendFulfillmentComplete($c->get('order_id')),
                    'handleCancellation' => function (ContextManager $c): void {
                        if ($c->get('payment_id')) {
                            PaymentService::refund($c->get('payment_id'));
                        }
                    },
                ],
            ],
        );
    }
}

Usage

php
$machine = OrderFulfillmentMachine::create();
$machine->state->context->set('order_id', $order->id);

// Events can arrive in any order - each region processes independently
$machine->send(['type' => 'DOCS_READY']);
$machine->send(['type' => 'PAYMENT_SUCCESS', 'payload' => ['payment_id' => 'pay_123']]);
$machine->send(['type' => 'PICKED']);
$machine->send(['type' => 'PACKED']);
$machine->send(['type' => 'SHIPPED', 'payload' => ['tracking_number' => '1Z999...']]);

// All regions final - automatically transitions to 'fulfilled'
expect($machine->state->matches('fulfilled'))->toBeTrue();

Check Region Status

php
// Check individual regions
$state->matches('processing.payment.charged');   // true
$state->matches('processing.shipping.packing');  // true

// Cancel from any state
$machine->send(['type' => 'CANCEL']);
expect($machine->state->matches('cancelled'))->toBeTrue();

Released under the MIT License.