Results
Results define the output of a state machine when it reaches a final state. They compute and return values based on the final context.
Basic Result
php
use Tarfinlabs\EventMachine\Behavior\ResultBehavior;
class OrderResultBehavior extends ResultBehavior
{
public function __invoke(ContextManager $context): array
{
return [
'orderId' => $context->orderId,
'total' => $context->total,
'status' => 'completed',
];
}
}Defining Results
In State Configuration
php
'states' => [
'processing' => [
'on' => ['COMPLETE' => 'completed'],
],
'completed' => [
'type' => 'final',
'result' => 'getOrderResult',
],
],
// In behavior
'results' => [
'getOrderResult' => OrderResultBehavior::class,
],Direct Class Reference
php
'completed' => [
'type' => 'final',
'result' => OrderResultBehavior::class,
],Inline Result
php
'results' => [
'getOrderResult' => fn(ContextManager $ctx) => [
'orderId' => $ctx->orderId,
'total' => $ctx->total,
],
],Accessing Results
php
$machine = OrderMachine::create();
// Process to final state
$machine->send(['type' => 'SUBMIT']);
$machine->send(['type' => 'COMPLETE']);
// Get result
$result = $machine->result();
// Result contains whatever the ResultBehavior returns
echo $result['orderId'];
echo $result['total'];Different Results for Different Final States
php
'states' => [
'processing' => [
'on' => [
'COMPLETE' => 'success',
'CANCEL' => 'cancelled',
'FAIL' => 'failed',
],
],
'success' => [
'type' => 'final',
'result' => 'getSuccessResult',
],
'cancelled' => [
'type' => 'final',
'result' => 'getCancelledResult',
],
'failed' => [
'type' => 'final',
'result' => 'getFailedResult',
],
],
'results' => [
'getSuccessResult' => fn($ctx) => [
'status' => 'success',
'orderId' => $ctx->orderId,
'message' => 'Order completed successfully',
],
'getCancelledResult' => fn($ctx) => [
'status' => 'cancelled',
'reason' => $ctx->cancellationReason,
],
'getFailedResult' => fn($ctx) => [
'status' => 'failed',
'error' => $ctx->errorMessage,
'retryable' => true,
],
],Result Parameters
Results receive injected parameters:
php
class ComplexResultBehavior extends ResultBehavior
{
public function __invoke(
ContextManager $context,
State $state,
EventCollection $history,
): array {
return [
'orderId' => $context->orderId,
'finalState' => $state->currentStateDefinition->id,
'eventCount' => $history->count(),
'duration' => $this->calculateDuration($history),
];
}
private function calculateDuration(EventCollection $history): int
{
$first = $history->first()->created_at;
$last = $history->last()->created_at;
return $first->diffInSeconds($last);
}
}Dependency Injection
php
class OrderResultBehavior extends ResultBehavior
{
public function __construct(
private readonly OrderService $orderService,
private readonly ReceiptGenerator $receiptGenerator,
) {}
public function __invoke(ContextManager $context): array
{
$order = $this->orderService->find($context->orderId);
$receipt = $this->receiptGenerator->generate($order);
return [
'order' => $order->toArray(),
'receiptUrl' => $receipt->url,
'downloadUrl' => $receipt->downloadUrl,
];
}
}Practical Examples
Order Completion Result
php
class OrderCompletedResult extends ResultBehavior
{
public function __invoke(ContextManager $context): array
{
return [
'orderId' => $context->orderId,
'orderNumber' => $context->orderNumber,
'items' => $context->items,
'subtotal' => $context->subtotal,
'tax' => $context->tax,
'shipping' => $context->shipping,
'total' => $context->total,
'status' => 'completed',
'completedAt' => now()->toIso8601String(),
'estimatedDelivery' => $context->estimatedDelivery,
];
}
}Loan Application Result
php
class LoanApprovalResult extends ResultBehavior
{
public function __invoke(ContextManager $context): array
{
return [
'applicationId' => $context->applicationId,
'status' => 'approved',
'loanAmount' => $context->approvedAmount,
'interestRate' => $context->interestRate,
'termMonths' => $context->termMonths,
'monthlyPayment' => $this->calculateMonthlyPayment($context),
'approvedBy' => $context->approver,
'approvedAt' => now()->toIso8601String(),
'conditions' => $context->conditions ?? [],
];
}
private function calculateMonthlyPayment(ContextManager $context): float
{
$principal = $context->approvedAmount;
$rate = $context->interestRate / 12 / 100;
$months = $context->termMonths;
return $principal * ($rate * pow(1 + $rate, $months))
/ (pow(1 + $rate, $months) - 1);
}
}
class LoanRejectionResult extends ResultBehavior
{
public function __invoke(ContextManager $context): array
{
return [
'applicationId' => $context->applicationId,
'status' => 'rejected',
'reasons' => $context->rejectionReasons,
'canReapply' => $context->canReapply,
'reapplyAfter' => $context->reapplyAfter,
];
}
}Workflow Result
php
class WorkflowCompletedResult extends ResultBehavior
{
public function __invoke(
ContextManager $context,
EventCollection $history,
): array {
$approvals = $history
->filter(fn($e) => $e->type === 'APPROVE')
->map(fn($e) => [
'approver' => $e->payload['approver'],
'timestamp' => $e->created_at->toIso8601String(),
'comment' => $e->payload['comment'] ?? null,
]);
return [
'requestId' => $context->requestId,
'status' => 'approved',
'approvals' => $approvals->toArray(),
'totalApprovers' => $approvals->count(),
'processingTime' => $this->getProcessingTime($history),
];
}
private function getProcessingTime(EventCollection $history): string
{
$start = $history->first()->created_at;
$end = $history->last()->created_at;
return $start->diffForHumans($end, true);
}
}Quiz/Game Result
php
class QuizResultBehavior extends ResultBehavior
{
public function __invoke(ContextManager $context): array
{
$total = count($context->questions);
$correct = $context->correctAnswers;
$percentage = ($correct / $total) * 100;
return [
'score' => $correct,
'total' => $total,
'percentage' => round($percentage, 2),
'grade' => $this->getGrade($percentage),
'passed' => $percentage >= 70,
'timeTaken' => $context->timeTaken,
'answers' => $context->answers,
];
}
private function getGrade(float $percentage): string
{
return match (true) {
$percentage >= 90 => 'A',
$percentage >= 80 => 'B',
$percentage >= 70 => 'C',
$percentage >= 60 => 'D',
default => 'F',
};
}
}Result Arguments
Pass arguments to results:
php
'completed' => [
'type' => 'final',
'result' => 'formatResult:detailed',
],
'results' => [
'formatResult' => function (
ContextManager $context,
array $arguments,
) {
$format = $arguments[0] ?? 'simple';
if ($format === 'detailed') {
return [...detailed result...];
}
return [...simple result...];
},
],Testing Results
php
it('returns correct result when completed', function () {
$machine = OrderMachine::create();
$machine->send(['type' => 'ADD_ITEM', 'payload' => ['item' => [...]]]);
$machine->send(['type' => 'CHECKOUT']);
$machine->send(['type' => 'COMPLETE']);
$result = $machine->result();
expect($result)->toHaveKeys(['orderId', 'total', 'status'])
->and($result['status'])->toBe('completed')
->and($result['total'])->toBeGreaterThan(0);
});
it('returns different result when cancelled', function () {
$machine = OrderMachine::create();
$machine->send(['type' => 'ADD_ITEM', 'payload' => ['item' => [...]]]);
$machine->send(['type' => 'CANCEL', 'payload' => ['reason' => 'Changed mind']]);
$result = $machine->result();
expect($result['status'])->toBe('cancelled')
->and($result['reason'])->toBe('Changed mind');
});Best Practices
1. Include All Relevant Data
php
return [
'orderId' => $context->orderId,
'status' => 'completed',
'total' => $context->total,
'items' => $context->items,
'createdAt' => $context->createdAt,
'completedAt' => now()->toIso8601String(),
];2. Format for API Response
php
return [
'data' => [
'id' => $context->orderId,
'attributes' => [...],
],
'meta' => [
'processingTime' => $duration,
],
];3. Handle Missing Data
php
return [
'orderId' => $context->orderId ?? 'unknown',
'total' => $context->total ?? 0,
'notes' => $context->notes ?? [],
];4. Use Different Results for Different Outcomes
php
'success' => ['type' => 'final', 'result' => SuccessResult::class],
'failed' => ['type' => 'final', 'result' => FailureResult::class],
'cancelled' => ['type' => 'final', 'result' => CancelledResult::class],