PHP code example of benrowe / stateflow

1. Go to this page and download the library: Download benrowe/stateflow library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

benrowe / stateflow example snippets


use BenRowe\StateFlow\StateFlow;
use BenRowe\StateFlow\Configuration;

// Define your state
class Order implements State {
    public function __construct(
        private string $status,
        private ?string $paymentId = null,
    ) {}

    public function with(array $changes): State {
        return new self(
            status: $changes['status'] ?? $this->status,
            paymentId: $changes['paymentId'] ?? $this->paymentId,
        );
    }

    public function toArray(): array {
        return ['status' => $this->status, 'paymentId' => $this->paymentId];
    }
}

// Configure the workflow
$stateFlow = new StateFlow(
    configProvider: fn($state, $delta) => new Configuration(
        transitionGates: [new CanProcessGate()],  // Must pass to proceed
        actions: [
            new ChargePaymentAction(),   // Execute in order
            new ReserveInventoryAction(), // Skip if guard fails
            new SendConfirmationAction(),
        ],
    ),
    eventDispatcher: new Logger(),      // See everything that happens
    lockProvider: new RedisLock($redis), // Prevent race conditions
);

// Execute transition with automatic locking
$order = new Order('pending');
$worker = $stateFlow->transition($order, new ArrayDelta(['status' => 'processing']));
$context = $worker->execute();


if ($context->isCompleted()) {
    echo "Order processed!";
} elseif ($context->isPaused()) {
    // Action paused (e.g., waiting for external API)
    // Lock is HELD across pause
    saveToDatabase($context->serialize());

    // Resume hours later...
    $resumedWorker = $stateFlow->fromContext($context);
    $resumedWorker->execute();
}

// Just this
$worker = $stateFlow->transition($state, new ArrayDelta(['status' => 'published']));
$context = $worker->execute();


// Not this
$worker = $stateFlow->transition($state, new ArrayDelta(['status' => 'published', 'author' => 'same', 'created' => 'same', ...]));
$context = $worker->execute();

$worker = $stateFlow->transition($state, new ArrayDelta(['status' => 'published']));

// 1. Run gates first
$gateResult = $worker->runGates();

// 2. Then run actions if gates pass
if (!$gateResult->shouldStopTransition()) {
    $context = $worker->runActions();
}

// Or let actions pause themselves for async operations
class ProcessVideoAction implements Action {
    public function execute(ActionContext $context): ActionResult {
        $job = dispatch(new VideoProcessingJob());

        // Pause execution, lock is held
        return ActionResult::pause(metadata: ['jobId' => $job->id]);
    }
}

// Resume later when ready
$resumedWorker = $stateFlow->fromContext($pausedContext);
$resumedWorker->execute();

$lockProvider = new RedisLockProvider($redis, $config);
$stateFlow = new StateFlow(
    configProvider: $configProvider,
    lockProvider: $lockProvider,
);

// This transition will be automatically locked
$worker = $stateFlow->transition($state, new ArrayDelta(['status' => 'published']));
$context = $worker->execute();

class MyEventDispatcher implements EventDispatcher {
    public function dispatch(Event $event): void {
        match (true) {
            $event instanceof TransitionStarting => $this->log('Starting...'),
            $event instanceof GateEvaluated => $this->log('Gate: ' . $event->result),
            $event instanceof ActionExecuted => $this->log('Action done'),
            $event instanceof TransitionCompleted => $this->log('Complete!'),
        };
    }
}

class CanPublishGate implements Gate {
    public function evaluate(GateContext $context): GateResult {
        return $context->currentState->hasContent()
            ? GateResult::ALLOW
            : GateResult::DENY;
    }
}

class NotifyAction implements Action, Guardable {
    public function gate(): Gate {
        return new HasSubscribersGate();
    }

    public function execute(ActionContext $context): ActionResult {
        // Only runs if HasSubscribersGate passes
    }
}

// 1. Define state with your domain model
class OrderState implements State {
    public function __construct(
        private string $id,
        private string $status,
        private float $total,
        private ?string $paymentId = null,
    ) {}

    public function with(array $changes): State {
        return new self(
            id: $this->id,
            status: $changes['status'] ?? $this->status,
            total: $changes['total'] ?? $this->total,
            paymentId: $changes['paymentId'] ?? $this->paymentId,
        );
    }

    public function toArray(): array { /* ... */ }
}

// 2. Configure workflow based on transition type
$configProvider = function(State $state, Delta $delta): Configuration {
    return match ($delta->get('status')) {
        'processing' => new Configuration(
            transitionGates: [new HasInventoryGate($inventory)],
            actions: [
                new ChargePaymentAction($paymentGateway),
                new ReserveInventoryAction($inventory),
                new SendEmailAction($mailer),
            ],
        ),
        'shipped' => new Configuration(
            transitionGates: [new HasPaymentGate()],
            actions: [new CreateShipmentAction($shipping)],
        ),
        default => new Configuration(),
    };
};

// 3. Create state flow with observability and locking
$stateFlow = new StateFlow(
    configProvider: $configProvider,
    eventDispatcher: new MetricsDispatcher(),
    lockProvider: new RedisLockProvider($redis),
    lockKeyProvider: new class implements LockKeyProvider {
        public function getLockKey(State $state, Delta $delta): string {
            return "order:" . $state->toArray()['id'];
        }
    },
);

// 4. Execute with race protection
try {
    $order = new OrderState('ORD-123', 'pending', 99.99);
    $worker = $stateFlow->transition($order, new ArrayDelta(['status' => 'processing']));
    $context = $worker->execute();

    if ($context->isCompleted()) {
        return response()->json(['status' => 'success']);
    }

} catch (LockAcquisitionException $e) {
    // Another request is processing this order
    return response()->json(['error' => 'Order is being processed'], 409);
}