PHP code example of discovery-ukraine / saga-lara-flow
1. Go to this page and download the library: Download discovery-ukraine/saga-lara-flow 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/ */
discovery-ukraine / saga-lara-flow example snippets
use DiscoveryUkraine\SagaLaraFlow\Workflow;
class CheckoutWorkflow extends Workflow
{
public function handle(string $orderId): array
{
$charge = $this->action(ChargeCard::class, $orderId)
->compensateWith(RefundCard::class, $orderId)
->run();
$this->action(ReserveStock::class, $orderId)
->compensateWith(ReleaseStock::class, $orderId)
->run();
$this->action(ShipOrder::class, $orderId)->run();
return ['charge' => $charge];
}
}
use DiscoveryUkraine\SagaLaraFlow\Facades\SagaFlow;
$run = SagaFlow::create(CheckoutWorkflow::class)
->withArguments('order-42')
->withTags(['tenant' => 'acme', 'order' => 'order-42'])
->run(); // dispatched onto the queue
use DiscoveryUkraine\SagaLaraFlow\Workflow;
class ProvisionAccountWorkflow extends Workflow
{
public function handle(string $email): array
{
$tenantId = $this->action(CreateTenant::class, $email)->run();
$this->action(SendWelcomeEmail::class, $email)->run();
return ['tenant' => $tenantId];
}
}
use DiscoveryUkraine\SagaLaraFlow\Action;
class CreateTenant extends Action
{
public function handle(TenantRepository $tenants, string $email): string
{
return $tenants->provision($email)->id;
}
}
use DiscoveryUkraine\SagaLaraFlow\Facades\SagaFlow;
$run = SagaFlow::create(ProvisionAccountWorkflow::class)
->withArguments('[email protected]')
->run(); // queued; returns a Pending FlowRun immediately
// or run it inline, driving every step in-process:
$run = SagaFlow::create(ProvisionAccountWorkflow::class)
->withArguments('[email protected]')
->runSync(); // returns a Completed FlowRun
class ChargeCard extends Action
{
public int $tries = 3; // up to 3 attempts when queued
public int $timeout = 30; // seconds per attempt
public function handle(PaymentGateway $gateway, string $orderId): string
{
return $gateway->charge($orderId);
}
}
use DiscoveryUkraine\SagaLaraFlow\Exceptions\ActionFailedException;
use DiscoveryUkraine\SagaLaraFlow\Exceptions\FlowExpiredException;
try {
$this->action(ChargeCard::class, $orderId)->run();
} catch (ActionFailedException $e) {
// retries exhausted — decide what the workflow does next
} catch (FlowExpiredException $e) {
// the step (or run) passed its deadline
}
public function handle(string $orderId): void
{
$this->action(ChargeCard::class, $orderId)
->compensateWith(RefundCard::class, $orderId)
->run();
$this->action(ReserveStock::class, $orderId)
->compensateWith(ReleaseStock::class, $orderId)
->run();
// If this throws, ReleaseStock then RefundCard run automatically.
$this->action(ShipOrder::class, $orderId)->run();
}
use DiscoveryUkraine\SagaLaraFlow\Enums\CompensationFailurePolicy;
$this->saga()
->onCompensationFailure(CompensationFailurePolicy::Continue) // keep rolling back even if one undo fails
->compensateInParallel() // run the group's undos concurrently
->step(ChargeCard::class, $orderId)->compensateWith(RefundCard::class, $orderId)
->step(ReserveStock::class, $orderId)->compensateWith(ReleaseStock::class, $orderId)
->run();
public function handle(): void
{
$decision = $this->awaitSignal('approval'); // suspends until delivered
if (($decision['approved'] ?? false) === true) {
$this->action(Publish::class)->run();
}
}
$this->action(ChargeCard::class, $orderId)
->compensateWith(RefundCard::class, $orderId)
->retryOnSignal(
'balance-refilled',
maxRetries: 3, // null = unbounded, never negative
waitSeconds: 86400, // how long one wait may last
only: [InsufficientBalanceException::class], // null = park on any exception
// the final say, for what the class alone cannot express
when: fn (RetryContext $context): bool => $context->failure->code !== 422,
)
->run();
final class BalanceRefillRecovery extends RetryPolicy
{
public function signal(): string
{
return 'balance-refilled';
}
public function maxRetries(): ?int
{
return 3;
}
public function only(): ?array
{
return [InsufficientBalanceException::class];
}
public function shouldRetry(RetryContext $context): bool
{
return $context->failure->code !== 422;
}
}
$this->action(ChargeCard::class, $orderId)->retryOnSignal(new BalanceRefillRecovery)->run();
public function handle(): void
{
$reference = $this->sideEffect('reference', fn () => (string) Str::uuid());
$this->action(CreateInvoice::class, $reference)->run();
}
use DiscoveryUkraine\SagaLaraFlow\Enums\ParallelFailurePolicy;
[$a, $b, $c] = $this->parallel()
->action(FetchPricing::class, $sku)
->action(FetchInventory::class, $sku)
->action(FetchReviews::class, $sku)
->failFast() // cancel the block on the first failure (default)
->run();
use DiscoveryUkraine\SagaLaraFlow\Enums\ChildClosePolicy;
public function handle(): array
{
$result = $this->child(ShipmentWorkflow::class, ['order-42'])
->closePolicy(ChildClosePolicy::Cancel) // what happens to the child if the parent closes
->run();
return ['shipment' => $result];
}
SagaFlow::create(CheckoutWorkflow::class)
->withTags(['tenant' => 'acme', 'channel' => 'web'])
->run();
// inside handle():
$this->tag('priority', 'high');
// or several at once — idempotent across replays, re-tagging a key overwrites it
$this->tags(['priority' => 'high', 'attempt' => 2, 'orders' => null]);
// from outside — tags() reads, withTags() writes
SagaFlow::loadFlow($runId)
->tag('payment-failed')
->withTags(['attempt' => 2]);
use DiscoveryUkraine\SagaLaraFlow\Enums\FlowStatus;
$stuck = SagaFlow::query()
->whereWorkflow(CheckoutWorkflow::class)
->whereTag('tenant', 'acme')
->waiting()
->before(now()->subHour())
->get(); // Collection<FlowRun>
$handles = SagaFlow::query()->running()->handles(); // Collection<FlowHandle>
$count = SagaFlow::query()->failed()->count();
// runs whose wait is still open, from either seam
SagaFlow::query()->whereAwaitingSignal('approval')->get();
// runs holding a step parked by retryOnSignal()
SagaFlow::query()->whereAwaitingRetrySignal('balance-refilled')->handles();
use Illuminate\Support\Facades\Schedule;
Schedule::command('saga-flow:monitor')->everyMinute();
$run = SagaFlow::create(CheckoutWorkflow::class)
->withArguments('order-42')
->runSync();
$run->status; // FlowStatus::Completed
$run->result; // the value handle() returned