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:
// 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:
- Cache driver must support atomic locks — Redis or database (not
arrayorfile) - Queue worker must be running — Region jobs are dispatched to the queue
- Entry actions must be idempotent — Jobs may be retried on failure
- Parallel regions should write to different context keys — Shared keys trigger a
PARALLEL_CONTEXT_CONFLICTevent (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.
'parallel_dispatch' => [
'region_timeout' => 120, // Trigger @fail after 2 minutes (0 = disabled)
],New Files
| File | Description |
|---|---|
src/Jobs/ParallelRegionJob.php | Internal queue job for region entry actions |
src/Jobs/ParallelRegionTimeoutJob.php | Delayed check job for stuck parallel state detection |
src/Locks/MachineLockManager.php | Database-based lock management |
src/Support/ArrayUtils.php | Shared recursive array merge/diff utilities |
New Internal Events
v5.0 adds seven internal events for parallel dispatch observability:
| Event | Purpose |
|---|---|
PARALLEL_REGION_ENTER | Region job completed and persisted context |
PARALLEL_REGION_GUARD_ABORT | Under-lock guard discarded work (machine moved on) |
PARALLEL_CONTEXT_CONFLICT | Sibling region overwrote a shared context key (LWW) |
PARALLEL_REGION_STALLED | Region entry action completed without advancing (no raise) |
PARALLEL_REGION_TIMEOUT | Parallel state did not complete within region_timeout seconds |
PARALLEL_DONE | All regions reached final, @done fired |
PARALLEL_FAIL | Region job failed after all retries |
All events are persisted as MachineEvent records — durable audit trail, not logs.
Migration Steps
- Update the package:
composer update tarfinlabs/event-machine:^5.0- Publish and run migrations (adds
machine_lockstable for parallel dispatch locking):
php artisan vendor:publish --tag=machine-migrations
php artisan migrate- Publish config if not already done:
php artisan vendor:publish --tag=machine-config- Add parallel dispatch keys to your
config/machine.php - Set
MACHINE_PARALLEL_DISPATCH_ENABLED=truein.envwhen ready - 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):
··· 1 hidden line
class MyAction extends ActionBehavior
{
public function __invoke($context, $event): void
{
// Parameters were positional
}
}After (v3.x):
··· 3 hidden lines
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):
$context->data['key'] = 'value';After (v3.x):
$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):
$machine->state->value === 'pending';After (v3.x):
$machine->state->matches('pending');The matches() method handles machine ID prefixing automatically.
4. Event Class Registration
Before (v2.x):
'on' => [
'SUBMIT' => [...],
],
'behavior' => [
'events' => [
'SUBMIT' => SubmitEvent::class,
],
],After (v3.x):
// 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:
··· 2 hidden lines
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
composer require tarfinlabs/event-machine:^3.0Step 2: Run Migrations
php artisan migrateNew columns may be added to the machine_events table.
Step 3: Update Behavior Type Hints
// Before
public function __invoke($context, $event): void
// After
public function __invoke(ContextManager $context, EventBehavior $event): voidStep 4: Update State Checks
// Before
if ($machine->state->value === 'order.pending') {
// After
if ($machine->state->matches('pending')) {Step 5: Update Context Access
// 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:
// 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:
'on' => [
'SUBMIT' => [
'target' => 'processing',
'calculators' => 'calculateTotal',
'guards' => 'hasPositiveTotal',
'actions' => 'processOrder',
],
],Event Class Keys
Use event classes directly as transition keys:
use App\Events\SubmitEvent;
'on' => [
SubmitEvent::class => [
'target' => 'processing',
],
],Improved Type Safety
Custom context classes with full validation:
··· 1 hidden line
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:
php artisan machine:archive-eventsUpgrading to v2.x
From v1.x
State Value Format
Before (v1.x):
$machine->state->value; // 'pending'After (v2.x):
$machine->state->value; // ['machine.pending']State values are now arrays containing the full path.
Machine Creation
Before (v1.x):
$machine = new OrderMachine();
$machine->start();After (v2.x):
$machine = OrderMachine::create();Use the static create() method.
Event Sending
Before (v1.x):
$machine->dispatch('SUBMIT', ['key' => 'value']);After (v2.x):
$machine->send([
'type' => 'SUBMIT',
'payload' => ['key' => 'value'],
]);Events use array format with type and payload keys.
Version Compatibility
| EventMachine | PHP | Laravel |
|---|---|---|
| 5.x | 8.3+ | 11.x, 12.x |
| 4.x | 8.3+ | 11.x, 12.x |
| 3.x | 8.2+ | 10.x, 11.x, 12.x |
| 2.x | 8.1+ | 9.x, 10.x |
| 1.x | 8.0+ | 8.x, 9.x |
Getting Help
If you encounter issues during upgrade:
- Check the GitHub Issues
- Review the Changelog
- Open a new issue with your upgrade scenario
Related
- Installation - Fresh installation guide
- Your First Machine - Getting started tutorial