Skip to content

Upgrading Guide

Guide for upgrading between EventMachine versions.

Upgrading to v5.0

v5.0 adds true parallel dispatch for parallel states with configurable region timeout.

New Feature: Parallel Dispatch

Opt-in concurrent execution of parallel region entry actions via Laravel queue jobs. Existing parallel state machines continue to work unchanged — parallel dispatch is disabled by default.

Enable Parallel Dispatch

Publish and update the config:

php
// config/machine.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),
    ],
];

Requirements

When parallel dispatch is enabled:

  1. Cache driver must support atomic locks — Redis or database (not array or file)
  2. Queue worker must be running — Region jobs are dispatched to the queue
  3. Entry actions must be idempotent — Jobs may be retried on failure
  4. Parallel regions should write to different context keys — Shared keys trigger a PARALLEL_CONTEXT_CONFLICT event (LWW applies)

New Feature: Region Timeout

When region_timeout is set (seconds), a delayed check job fires after the configured duration. If the parallel state has not completed (any region still not final), it triggers @fail on the parallel state.

php
'parallel_dispatch' => [
    'region_timeout' => 120, // Trigger @fail after 2 minutes (0 = disabled)
],

New Files

FileDescription
src/Jobs/ParallelRegionJob.phpInternal queue job for region entry actions
src/Jobs/ParallelRegionTimeoutJob.phpDelayed check job for stuck parallel state detection
src/Locks/MachineLockManager.phpDatabase-based lock management
src/Support/ArrayUtils.phpShared recursive array merge/diff utilities

New Internal Events

v5.0 adds seven internal events for parallel dispatch observability:

EventPurpose
PARALLEL_REGION_ENTERRegion job completed and persisted context
PARALLEL_REGION_GUARD_ABORTUnder-lock guard discarded work (machine moved on)
PARALLEL_CONTEXT_CONFLICTSibling region overwrote a shared context key (LWW)
PARALLEL_REGION_STALLEDRegion entry action completed without advancing (no raise)
PARALLEL_REGION_TIMEOUTParallel state did not complete within region_timeout seconds
PARALLEL_DONEAll regions reached final, @done fired
PARALLEL_FAILRegion job failed after all retries

All events are persisted as MachineEvent records — durable audit trail, not logs.

Migration Steps

  1. Update the package:
bash
composer update tarfinlabs/event-machine:^5.0
  1. Publish and run migrations (adds machine_locks table for parallel dispatch locking):
bash
php artisan vendor:publish --tag=machine-migrations
php artisan migrate
  1. Publish config if not already done:
bash
php artisan vendor:publish --tag=machine-config
  1. Add parallel dispatch keys to your config/machine.php
  2. Set MACHINE_PARALLEL_DISPATCH_ENABLED=true in .env when ready
  3. Ensure your cache and queue drivers are configured

For full details, see Parallel Dispatch.


Upgrading to v3.x

Requirements

  • PHP 8.2+ (upgraded from 8.1)
  • Laravel 10.x, 11.x, or 12.x

Breaking Changes

1. Behavior Parameter Injection

Before (v2.x):

php

use Tarfinlabs\EventMachine\Behavior\ActionBehavior; class MyAction extends ActionBehavior
{
    public function __invoke($context, $event): void
    {
        // Parameters were positional
    }
}

After (v3.x):

php

use Tarfinlabs\EventMachine\Behavior\ActionBehavior; use Tarfinlabs\EventMachine\ContextManager; use Tarfinlabs\EventMachine\Behavior\EventBehavior; class MyAction extends ActionBehavior
{
    public function __invoke(ContextManager $context, EventBehavior $event): void
    {
        // Type-hinted parameters are injected
    }
}

Parameters are now injected based on type hints, not position. Ensure all behaviors use proper type hints.

2. ContextManager Changes

Before (v2.x):

php
$context->data['key'] = 'value';

After (v3.x):

php
$context->set('key', 'value');
// or
$context->key = 'value';

Direct array access is deprecated. Use get(), set(), and magic methods.

3. State Matching

Before (v2.x):

php
$machine->state->value === 'pending';

After (v3.x):

php
$machine->state->matches('pending');

The matches() method handles machine ID prefixing automatically.

4. Event Class Registration

Before (v2.x):

php
'on' => [
    'SUBMIT' => [...],
],
'behavior' => [
    'events' => [
        'SUBMIT' => SubmitEvent::class,
    ],
],

After (v3.x):

php
// Option 1: Event class as key (auto-registered)
'on' => [
    SubmitEvent::class => [...],
],

// Option 2: Explicit registration still works
'on' => [
    'SUBMIT' => [...],
],
'behavior' => [
    'events' => [
        'SUBMIT' => SubmitEvent::class,
    ],
],

Using event classes as transition keys is now supported.

5. Calculator Behavior

New in v3.x:

php

use Tarfinlabs\EventMachine\Behavior\CalculatorBehavior; use Tarfinlabs\EventMachine\ContextManager; class CalculateTotalCalculator extends CalculatorBehavior
{
    public function __invoke(ContextManager $context): void
    {
        $context->total = $context->subtotal + $context->tax;
    }
}

Calculators are a new behavior type that runs before guards.

Migration Steps

Step 1: Update Dependencies

bash
composer require tarfinlabs/event-machine:^3.0

Step 2: Run Migrations

bash
php artisan migrate

New columns may be added to the machine_events table.

Step 3: Update Behavior Type Hints

php
// Before
public function __invoke($context, $event): void

// After
public function __invoke(ContextManager $context, EventBehavior $event): void

Step 4: Update State Checks

php
// Before
if ($machine->state->value === 'order.pending') {

// After
if ($machine->state->matches('pending')) {

Step 5: Update Context Access

php
// Before
$machine->state->context->data['orderId']

// After
$machine->state->context->orderId
// or
$machine->state->context->get('orderId')

Step 6: Review Guard/Action Separation

Consider moving context modifications from guards to calculators:

php
// Before (v2.x) - guard modifying context
'guards' => [
    'validateAndCalculate' => function ($context) {
        $context->total = $context->subtotal * 1.1;  // Don't do this in guards
        return $context->total > 0;
    },
],

// After (v3.x) - separate concerns
'calculators' => [
    'calculateTotal' => function ($context) {
        $context->total = $context->subtotal * 1.1;
    },
],
'guards' => [
    'hasPositiveTotal' => function ($context) {
        return $context->total > 0;
    },
],

New Features in v3.x

Calculators

Run before guards to modify context:

php
'on' => [
    'SUBMIT' => [
        'target' => 'processing',
        'calculators' => 'calculateTotal',
        'guards' => 'hasPositiveTotal',
        'actions' => 'processOrder',
    ],
],

Event Class Keys

Use event classes directly as transition keys:

php
use App\Events\SubmitEvent;

'on' => [
    SubmitEvent::class => [
        'target' => 'processing',
    ],
],

Improved Type Safety

Custom context classes with full validation:

php

use Tarfinlabs\EventMachine\ContextManager; class OrderContext extends ContextManager
{
    public function __construct(
        #[Required]
        public string $orderId,

        #[Min(0)]
        public int $total = 0,
    ) {
        parent::__construct();
    }
}

Archive System

Event archival with compression:

bash
php artisan machine:archive-events

Upgrading to v2.x

From v1.x

State Value Format

Before (v1.x):

php
$machine->state->value; // 'pending'

After (v2.x):

php
$machine->state->value; // ['machine.pending']

State values are now arrays containing the full path.

Machine Creation

Before (v1.x):

php
$machine = new OrderMachine();
$machine->start();

After (v2.x):

php
$machine = OrderMachine::create();

Use the static create() method.

Event Sending

Before (v1.x):

php
$machine->dispatch('SUBMIT', ['key' => 'value']);

After (v2.x):

php
$machine->send([
    'type' => 'SUBMIT',
    'payload' => ['key' => 'value'],
]);

Events use array format with type and payload keys.


Version Compatibility

EventMachinePHPLaravel
5.x8.3+11.x, 12.x
4.x8.3+11.x, 12.x
3.x8.2+10.x, 11.x, 12.x
2.x8.1+9.x, 10.x
1.x8.0+8.x, 9.x

Getting Help

If you encounter issues during upgrade:

  1. Check the GitHub Issues
  2. Review the Changelog
  3. Open a new issue with your upgrade scenario

Released under the MIT License.