Skip to content

Configuration

This guide covers all configuration options for defining state machines.

Machine Configuration

The config array defines your machine's structure:

php
MachineDefinition::define(
    config: [
        'id' => 'order',
        'version' => '1.0.0',
        'initial' => 'pending',
        'delimiter' => '.',
        'context' => [...],
        'states' => [...],
        'should_persist' => true,
    ],
);

Root Configuration Keys

KeyTypeDefaultDescription
idstring'machine'Unique identifier for the machine
versionstringnullVersion string for tracking
initialstringFirst stateInitial state name
delimiterstring'.'Path separator for state IDs
contextarray|class[]Initial context or ContextManager class
statesarrayRequiredState definitions
onarraynullRoot-level transitions
should_persistbooltrueEnable database persistence
scenarios_enabledboolfalseDeprecated — use config/machine.php scenarios.enabled instead

Machine ID

Identifies the machine for persistence and debugging:

php
'id' => 'checkout-flow',

State IDs are prefixed with the machine ID:

  • checkout-flow.cart
  • checkout-flow.payment

Version

Track machine versions for migrations:

php
'version' => '2.0.0',

Initial State

Specify which state the machine starts in:

php
'initial' => 'idle',

If not specified, the first state in the states array is used.

Delimiter

Customize the path separator for nested state IDs:

php
'delimiter' => '/',  // Results in: order/checkout/payment

Default is . (dot): order.checkout.payment

Behavior Configuration

The behavior array maps names to implementations:

php
MachineDefinition::define(
    config: [...],
    behavior: [
        'actions' => [
            'sendEmailAction' => SendEmailAction::class,
            'logEventAction' => fn (ContextManager $context) => logger()->info('Event'),
        ],
        'guards' => [
            'isAuthenticatedGuard' => IsAuthenticatedGuard::class,
            'hasPermissionGuard' => fn (ContextManager $context) => $context->get('role') === 'admin',
        ],
        'calculators' => [
            'computeTotalCalculator' => ComputeTotalCalculator::class,
        ],
        'events' => [
            'ADD_ITEM' => AddItemEvent::class,
        ],
        'outputs' => [
            'orderSummaryOutput' => OrderSummaryOutput::class,
        ],
        'context' => OrderContext::class,
    ],
);

Behavior Types

TypeDescription
actionsSide effects during transitions
guardsConditions controlling transitions
calculatorsContext computations before guards
eventsCustom event classes
outputsFinal state output computation
contextCustom ContextManager class

Inline vs Class Behaviors

Both approaches work:

php
'actions' => [
    // Class reference
    'sendEmailAction' => SendEmailAction::class,

    // Inline closure
    'logEventAction' => function (ContextManager $context): void {
        logger()->info('Event logged');
    },
],

State Configuration

Each state supports these keys:

KeyTypeDescription
onarrayEvent-to-transition mappings
entrystring|arrayActions on state entry
exitstring|arrayActions on state exit
typestringState type ('final')
outputstring|arrayOutput behavior for final states
initialstringInitial child state
statesarrayChild state definitions
machinestring (FQCN)Child machine class for delegation
inputstring|array|ClosureData to pass to child machine. MachineInput FQCN, array, or closure. (renamed from with in v9)
failurestring (FQCN)MachineFailure class for typed error data on child @fail
metaarrayCustom metadata
descriptionstringHuman-readable description
php
'processing' => [
    'description' => 'Order is being processed',
    'entry' => 'startProcessingAction',
    'exit' => 'cleanupAction',
    'meta' => ['timeout' => 3600],
    'on' => [
        'COMPLETE' => 'done',
        'FAIL' => 'failed',
    ],
],

Transition Configuration

Transitions can be simple strings or detailed arrays:

KeyTypeDescription
targetstringDestination state
actionsstring|arrayActions during transition
guardsstring|arrayConditions to check
calculatorsstring|arrayPre-guard computations
descriptionstringHuman-readable description
php
'SUBMIT' => [
    'target' => 'submitted',
    'guards' => ['isValidGuard', 'canSubmitGuard'],
    'calculators' => 'prepareDataCalculator',
    'actions' => ['validateAction', 'saveAction', 'notifyAction'],
    'description' => 'Submit the form for review',
],

Persistence Configuration

Disabling Persistence

For ephemeral machines:

php
'should_persist' => false,

Useful for:

  • Testing
  • Short-lived operations
  • Memory-only state machines

Default Behavior

With should_persist => true (default):

  • Every transition creates a MachineEvent record
  • State can be restored from any point
  • Full history is maintained

Scenarios Configuration

Scenarios are configured in config/machine.php, not in the machine definition:

php
// config/machine.php
'scenarios' => [
    'enabled' => env('MACHINE_SCENARIOS_ENABLED', false),
],
KeyTypeDefaultDescription
scenarios.enabledboolfalseEnable scenario system. Set to true in staging only.

When enabled, scenarios provide behavior overrides for QA and staging environments via MachineScenario classes. See Scenarios for full documentation.

Configuration Validation

EventMachine validates your configuration at definition time:

Invalid Keys

php
// Throws: Invalid root level configuration keys: invalid_key
'invalid_key' => 'value',

Invalid State Type

php
// Throws: State 'foo' has invalid type: invalid
'foo' => ['type' => 'invalid'],

Final State Constraints

php
// Throws: Final state 'done' cannot have transitions
'done' => [
    'type' => 'final',
    'on' => ['RESTART' => 'initial'],  // Not allowed
],

Guarded Transition Order

php
// Throws: Default condition must be last
'PAY' => [
    ['target' => 'failed'],           // No guard - must be last!
    ['target' => 'paid', 'guards' => 'isValid'],
],

Complete Configuration Example

php
use Tarfinlabs\EventMachine\Definition\MachineDefinition;

MachineDefinition::define(
    config: [
        'id' => 'order',
        'version' => '1.0.0',
        'initial' => 'cart',
        'delimiter' => '.',
        'context' => OrderContext::class,
        'should_persist' => true,
        'states' => [
            'cart' => [
                'description' => 'Shopping cart',
                'meta' => ['icon' => 'shopping-cart'],
                'on' => [
                    'ADD_ITEM' => ['actions' => 'addItemAction'],
                    'CHECKOUT' => [
                        'target' => 'checkout',
                        'guards' => 'hasItemsGuard',
                    ],
                ],
            ],
            'checkout' => [
                'initial' => 'shipping',
                'states' => [
                    'shipping' => [
                        'entry' => 'loadShippingOptionsAction',
                        'on' => [
                            'SET_ADDRESS' => ['actions' => 'setAddressAction'],
                            'CONTINUE' => [
                                'target' => 'payment',
                                'guards' => 'hasAddressGuard',
                            ],
                        ],
                    ],
                    'payment' => [
                        'entry' => 'loadPaymentMethodsAction',
                        'on' => [
                            'PAY' => [
                                [
                                    'target' => 'confirmed',
                                    'guards' => 'paymentValidGuard',
                                    'actions' => ['processPaymentAction', 'reserveStockAction'],
                                ],
                                [
                                    'target' => 'payment',
                                    'actions' => 'showPaymentErrorAction',
                                ],
                            ],
                            'BACK' => 'shipping',
                        ],
                    ],
                    'confirmed' => [],
                ],
                'on' => [
                    'CANCEL' => 'cart',
                ],
            ],
            'processing' => [
                'entry' => 'startFulfillmentAction',
                'on' => [
                    'SHIP' => 'shipped',
                ],
            ],
            'shipped' => [
                'entry' => 'sendTrackingEmailAction',
                'on' => [
                    'DELIVER' => 'delivered',
                ],
            ],
            'delivered' => [
                'type' => 'final',
                'entry' => 'sendDeliveryConfirmationAction',
                'output' => 'orderSummaryOutput',
                'meta' => ['completed' => true],
            ],
        ],
    ],
    behavior: [
        'context' => OrderContext::class,
        'events' => [
            'ADD_ITEM' => AddItemEvent::class,
            'SET_ADDRESS' => SetAddressEvent::class,
            'PAY' => PaymentEvent::class,
        ],
        'actions' => [
            'addItemAction' => AddItemAction::class,
            'setAddressAction' => SetAddressAction::class,
            'processPaymentAction' => ProcessPaymentAction::class,
            'reserveStockAction' => ReserveStockAction::class,
            'startFulfillmentAction' => StartFulfillmentAction::class,
            'sendTrackingEmailAction' => SendTrackingEmailAction::class,
            'sendDeliveryConfirmationAction' => SendDeliveryConfirmationAction::class,
            'loadShippingOptionsAction' => LoadShippingOptionsAction::class,
            'loadPaymentMethodsAction' => LoadPaymentMethodsAction::class,
            'showPaymentErrorAction' => ShowPaymentErrorAction::class,
        ],
        'guards' => [
            'hasItemsGuard' => HasItemsGuard::class,
            'hasAddressGuard' => HasAddressGuard::class,
            'paymentValidGuard' => PaymentValidGuard::class,
        ],
        'outputs' => [
            'orderSummaryOutput' => OrderSummaryOutput::class,
        ],
    ],
);

Parallel Dispatch Configuration

Parallel dispatch runs region entry actions as concurrent queue jobs. Configure in config/machine.php:

php
return [
    'parallel_dispatch' => [
        'enabled'        => env('MACHINE_PARALLEL_DISPATCH_ENABLED', false),
        'queue'          => env('MACHINE_PARALLEL_DISPATCH_QUEUE', null),
        'lock_timeout'   => env('MACHINE_PARALLEL_DISPATCH_LOCK_TIMEOUT', 30),
        'lock_ttl'       => env('MACHINE_PARALLEL_DISPATCH_LOCK_TTL', 60),
        'job_timeout'    => env('MACHINE_PARALLEL_DISPATCH_JOB_TIMEOUT', 300),
        'job_tries'      => env('MACHINE_PARALLEL_DISPATCH_JOB_TRIES', 3),
        'job_backoff'    => env('MACHINE_PARALLEL_DISPATCH_JOB_BACKOFF', 30),
        'region_timeout' => env('MACHINE_PARALLEL_DISPATCH_REGION_TIMEOUT', 0),
    ],
];
KeyDefaultDescription
enabledfalseEnable concurrent dispatch of region entry actions
queuenullQueue name for region jobs (null = default)
lock_timeout30Max seconds for blocking lock acquisition
lock_ttl60Lock auto-expiry for stale lock cleanup
job_timeout300Laravel job execution timeout (seconds)
job_tries3Max retry attempts for failed region jobs
job_backoff30Seconds between retry attempts
region_timeout0Seconds before stuck parallel state triggers @fail (0 = disabled)

For details, see Parallel Dispatch.

Testing

For testing configuration options like should_persist, see Testing Overview.

Syntax Shorthands

EventMachine accepts both verbose and shorthand forms for common configuration patterns. Prefer the short form when no extra options are needed — it's easier to read and reduces noise.

Transitions

php
// ✅ Short — target only
'SUBMIT' => 'processing',

// Verbose equivalent
'SUBMIT' => ['target' => 'processing'],

// Use verbose only when adding guards, actions, or calculators
'SUBMIT' => [
    'target'  => 'processing',
    'guards'  => IsValidGuard::class,
    'actions' => LogAction::class,
],

Entry / Exit Actions

php
// ✅ Short — single action
'entry' => InitAction::class,
'exit'  => CleanupAction::class,

// Multiple actions require array
'entry' => [InitAction::class, NotifyAction::class],

Guards, Actions, Calculators on Transitions

php
// ✅ Short — single behavior
'guards'      => IsValidGuard::class,
'actions'     => LogAction::class,
'calculators' => TotalCalculator::class,

// Multiple behaviors require array
'guards' => [IsValidGuard::class, HasBalanceGuard::class],

@done / @fail (Child Delegation)

php
// ✅ Short — target only
'@done' => 'completed',
'@fail' => 'failed',

// Verbose — with actions
'@done' => [
    'target'  => 'completed',
    'actions' => CaptureOutputAction::class,
],

// Per-final-state routing
'@done.approved' => 'processing',
'@done.rejected' => 'declined',

Queue (Async Delegation)

php
// ✅ Short — default queue
'queue' => true,

// Named queue
'queue' => 'child-queue',

// Full config
'queue' => [
    'queue'      => 'child-queue',
    'connection' => 'redis',
    'retry'      => 3,
],

Quick Reference

ElementShort FormVerbose Form
Transition target'EVENT' => 'state''EVENT' => ['target' => 'state']
Forbidden event'EVENT' => null
Single entry action'entry' => Action::class'entry' => [Action::class]
Single exit action'exit' => Action::class'exit' => [Action::class]
Single guard'guards' => Guard::class'guards' => [Guard::class]
Single action'actions' => Action::class'actions' => [Action::class]
Single calculator'calculators' => Calc::class'calculators' => [Calc::class]
@done target'@done' => 'state''@done' => ['target' => 'state']
@fail target'@fail' => 'state''@fail' => ['target' => 'state']
Default queue'queue' => true'queue' => ['queue' => null]
Named queue'queue' => 'name''queue' => ['queue' => 'name']

Rule of Thumb

If you're only specifying a target or a single value, use the short form. Switch to verbose only when you need extra options (guards, actions, calculators, description).

Released under the MIT License.