Artisan Commands
EventMachine provides several Artisan commands for managing state machines.
machine:validate
Validate machine configuration for potential issues.
Usage
# Validate specific machine
php artisan machine:validate "App\Machines\OrderMachine"
# Validate all machines in project
php artisan machine:validate --allWhat It Checks
- Valid state configuration keys
- Final states without transitions
- Final states without children
- Required initial states for compound states
- Behavior references
- Typed contract declarations (
inputandfailureconfig keys reference validMachineInputandMachineFailuresubclasses) MachineOutputclasses on final states are valid subclasses
Example Output
Validating: App\Machines\OrderMachine
✓ State configuration is valid
✓ Final states have no transitions
✓ All behaviors are registered
✓ Initial states are defined
Validation passed!Error Examples
Validating: App\Machines\BrokenMachine
✗ State 'completed' is final but has transitions
✗ State 'processing' is compound but has no initial state
✗ Behavior 'unknownAction' is not registered
Validation failed with 3 errors.machine:uml
Generate PlantUML state diagrams for visualization.
Usage
# Generate UML for a machine
php artisan machine:uml "App\Machines\OrderMachine"
# Output to specific file
php artisan machine:uml "App\Machines\OrderMachine" --output=order.pumlExample Output
@startuml OrderMachine
[*] --> pending
state pending {
}
pending --> processing : SUBMIT [hasItems]
processing --> completed : COMPLETE
processing --> cancelled : CANCEL
state completed <<final>> {
}
state cancelled <<final>> {
}
completed --> [*]
cancelled --> [*]
@endumlRendering
Use PlantUML to render the diagram:
# Install PlantUML
brew install plantuml
# Render to PNG
plantuml order.puml
# Render to SVG
plantuml -tsvg order.pumlFeatures Shown
- States and nested states
- Transitions with event names
- Guards (in square brackets)
- Final states
- Initial states
machine:archive-events
Archive old machine events to compressed storage.
Usage
# Dispatch archival jobs to queue (default)
php artisan machine:archive-events
# Preview what would be dispatched
php artisan machine:archive-events --dry-run
# Run synchronously (testing only)
php artisan machine:archive-events --sync
# Custom dispatch limit per run
php artisan machine:archive-events --dispatch-limit=100Options
| Option | Description |
|---|---|
--dry-run | Preview without changes |
--sync | Run synchronously instead of queue |
--dispatch-limit=N | Max workflows to dispatch per run (default: 50) |
Example Output
Finding eligible machines for archival...
Configuration:
Days inactive: 30
Dispatch limit: 50
Dispatching archival jobs...
Dispatched: 50 workflows to queue
Run again to dispatch the next batch.Dry Run Output
php artisan machine:archive-events --dry-run
DRY RUN - No jobs will be dispatched
Found 1,234 machines eligible for archival:
- order: 456 machines
- payment: 389 machines
- fulfillment: 389 machines
Would dispatch: 50 jobs (dispatch_limit)
Remaining: 1,184 machinesmachine:archive-status
View archive summary and manage archived events.
Usage
# Show summary
php artisan machine:archive-status
# Restore archived events
php artisan machine:archive-status --restore=01HXYZ...Options
| Option | Description |
|---|---|
--restore=ID | Restore events from archive |
Output
Machine Events Archive Status
+----------+-----------+--------+--------+
| | Instances | Events | Size |
+----------+-----------+--------+--------+
| Active | 1,234 | 56,789 | - |
| Archived | 5,678 | 234,567| 180 MB |
+----------+-----------+--------+--------+
Compression: 85% saved (1.02 GB)machine:xstate
Export machine definition to XState v5 JSON for visualization in Stately Studio.
Usage
php artisan machine:xstate "App\Machines\OrderMachine"Maps states, transitions, guards, actions, and delegation (machine key → XState invoke blocks).
machine:process-timers
Sweep command for time-based events (after/every on transitions). Auto-registered via MachineServiceProvider — runs on schedule, no manual setup needed.
Usage
# Process timers for a specific machine class
php artisan machine:process-timers --class="App\Machines\OrderMachine"How It Works
- Discovers machine classes with timer-configured transitions
- Queries
machine_current_statesfor instances past deadline - Inserts
machine_timer_firesrecords (atomic dedup viainsertOrIgnore) - Dispatches
SendToMachineJobviaBus::batch
Configuration
// config/machine.php
'timers' => [
'resolution' => 'everyMinute',
'batch_size' => 100,
'backpressure_threshold' => 10000,
],machine:process-scheduled
Processes a scheduled event for machine instances. Called by MachineScheduler via Laravel Scheduler — not typically run manually.
Usage
php artisan machine:process-scheduled --class="App\Machines\OrderMachine" --event=CHECK_EXPIRYHow It Works
- Loads definition, finds resolver for the event
- Resolver returns root_event_ids, cross-checked against
machine_current_states - Null resolver auto-detects target states from idMap
- Dispatches
SendToMachineJobviaBus::batch
machine:timer-status
Display timer status for machine instances — useful for debugging.
Usage
php artisan machine:timer-statusShows: root_event_id, machine class, state, entered_at, timer key, last fired, fire count, status.
machine:paths
Enumerate all paths through a machine definition. Static analysis — no database needed.
# Console output
php artisan machine:paths "App\Machines\OrderMachine"
# JSON output for CI
php artisan machine:paths "App\Machines\OrderMachine" --json
# Increase path limit for large machines (default: 1000)
php artisan machine:paths "App\Machines\LargeMachine" --max-paths=5000What It Shows
- Machine stats: states, events, guards, actions, calculators, job actors, child machines, timers
- Child machine and job actor names with async/sync mode and queue info
- All terminal paths grouped by type: HAPPY, FAIL, TIMEOUT, LOOP, GUARD_BLOCK, DEAD_END
- Child machine/job class names on invoke state steps
- Parallel state per-region paths with combination count
- Guard and action details per path
- Unhandled child outcome warnings (child final states without parent @done.{state} routes)
Example Output
OrderMachine — Path Analysis
════════════════════════════
States: 4 (2 atomic, 2 final)
Events: 1
Guards: 0
Actions: 1
Job actors: 1
processing → PaymentJob (queue: default)
Child machines: 0
Timers: 0
Terminal paths: 2
HAPPY PATHS (→ completed): 1 path
──────────────────────────────────
#1 → idle
→ [START] processing (PaymentJob)
→ [@done] completed
Actions: capturePaymentAction
FAIL PATHS (→ failed): 1 path
──────────────────────────────
#2 → idle
→ [START] processing (PaymentJob)
→ [@fail] failedChild machine and job class names appear in parentheses after the invoke state (e.g., processing (PaymentJob)). The stats section lists each delegation with its async/sync mode and queue name.
If a child machine has final states that the parent doesn't handle via @done.{state} routing (and no catch-all @done), a warning is shown at the end:
⚠ UNHANDLED CHILD OUTCOMES:
processing → PaymentChildMachine
Child final states: approved, rejected, expired
Parent handles: @done.approved
Unhandled: rejected, expiredmachine:coverage
Report path coverage for a machine definition. Reads coverage data produced by tests.
# Run tests first to generate coverage data
composer test
# Then report coverage
php artisan machine:coverage "App\Machines\OrderMachine"
# JSON output
php artisan machine:coverage "App\Machines\OrderMachine" --json
# Fail CI if below threshold
php artisan machine:coverage "App\Machines\OrderMachine" --min=100
# Custom coverage file location
php artisan machine:coverage "App\Machines\OrderMachine" --from=path/to/coverage.jsonCoverage Matching
The command compares enumerated paths (static analysis) against observed paths (test runtime) using state-sequence matching. Enable tracking in tests with PathCoverageTracker::enable() and record paths via TestMachine::assertFinished().
Example Output
OrderMachine — Path Coverage
════════════════════════════
Coverage: 1/2 paths (50.0%)
✓ #1 idle→[START]→processing→[@done]→completed
Tested by: order_completes_successfully
✗ #2 idle→[START]→processing→[@fail]→failed
UNTESTED: 1 path
→ idle
→ [START] processing
→ [@fail] failedmachine:scenario
Generate a MachineScenario class by analyzing the machine definition and resolving the path from source to target.
Usage
# Generate scenario class
php artisan machine:scenario AtAllocation CarSalesMachine \
awaiting_customer_start CustomerStartedEvent allocation
# Preview without writing
php artisan machine:scenario AtAllocation CarSalesMachine \
awaiting_customer_start CustomerStartedEvent allocation --dry-run
# Overwrite existing file
php artisan machine:scenario AtAllocation CarSalesMachine \
awaiting_customer_start CustomerStartedEvent allocation --force
# Select specific path when multiple exist
php artisan machine:scenario AtAllocation CarSalesMachine \
awaiting_customer_start CustomerStartedEvent allocation --path=1Arguments
| Argument | Description |
|---|---|
name | Scenario class name (Scenario suffix auto-added if missing) |
machine | Machine class FQCN |
source | Source state route (full or partial) |
event | Triggering event (class FQCN or event type string) |
target | Target state route (full or partial, supports deep targets) |
Options
| Option | Description |
|---|---|
--dry-run | Print generated file to stdout without writing |
--force | Overwrite existing scenario file |
--path=N | Select path by index when multiple paths exist (default: 0) |
The command classifies each intermediate state (transient, delegation, interactive, parallel) and generates appropriate plan() entries with TODO comments. Supports deep targets (cross-delegation) with automatic child scenario discovery.
See Scenarios — Scaffold Command for full details.
machine:scenario-validate
Validate all scenarios against their machine definitions. Catches structural errors and broken paths without running machines.
Usage
# Validate all scenarios for all machines
php artisan machine:scenario-validate
# Validate scenarios for a specific machine
php artisan machine:scenario-validate "App\Machines\CarSalesMachine"
# Validate a single scenario
php artisan machine:scenario-validate --scenario=AtCheckingProtocolScenarioWhat It Checks
Level 1 — Static validation: machine class exists, source/target/event valid, plan() routes exist, behavior classes exist, delegation outcomes on correct states, child scenario machine matches.
Level 2 — Path validation: path exists from source to target, @continue events lead toward target, deep target child scenarios exist.
Output
Validating scenarios...
CarSalesMachine (5 scenarios)
✓ AtVerificationScenario idle → verification
✓ AtCheckingProtocolScenario idle → checking_protocol
✗ AtAllocationScenario idle → allocation
State route 'checking_protocols' not found in machine definition
4 passed, 1 failedExit code 0 = all valid, exit code 1 = failures found. Suitable for CI/CD pipelines.
See Scenarios — Validation Command for full details.
Scheduling Commands
Add commands to your scheduler:
// app/Console/Kernel.php
protected function schedule(Schedule $schedule): void
{
// Fan-out archival: dispatches individual jobs per workflow
$schedule->command('machine:archive-events')
->everyFiveMinutes()
->withoutOverlapping()
->onOneServer()
->runInBackground();
// Weekly validation check
$schedule->command('machine:validate --all')
->weekly()
->mondays()
->at('06:00')
->emailOutputOnFailure('admin@example.com');
}Custom Commands
Create custom commands for your machines:
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Machines\OrderMachine;
use Tarfinlabs\EventMachine\Models\MachineEvent;
class OrderMachineStatsCommand extends Command
{
protected $signature = 'orders:stats';
protected $description = 'Show order machine statistics';
public function handle(): void
{
$stats = MachineEvent::where('machine_id', 'order')
->selectRaw('
COUNT(DISTINCT root_event_id) as machines,
COUNT(*) as events,
MIN(created_at) as first_event,
MAX(created_at) as last_event
')
->first();
$this->table(
['Metric', 'Value'],
[
['Total Machines', $stats->machines],
['Total Events', $stats->events],
['First Event', $stats->first_event],
['Last Event', $stats->last_event],
]
);
}
}
::: tip Testing
For testing artisan commands like `machine:process-timers` and `machine:process-scheduled`, see [Recipes](/testing/recipes).
:::