Execute non-deterministic code safely within workflow execution
Side effects provide a way to execute non-deterministic code within workflows while maintaining determinism during replay. This is essential for operations like generating random numbers, reading the current time, or making external API calls.
Workflows must be deterministic - they must produce the same result when replayed. However, some operations are inherently non-deterministic (random numbers, timestamps, UUIDs). Side effects solve this by caching the result on first execution and returning the cached value during replay.
use function Workflow\sideEffect;// ✓ Deterministic - safe for replayclass GoodWorkflow extends Workflow{ public function execute() { $randomValue = yield sideEffect(fn () => random_int(1, 1000)); yield activity(ProcessActivity::class, $randomValue); // During replay, sideEffect() returns the cached value // Determinism is maintained! return 'completed'; }}
The first time a side effect executes, it runs the callable and stores the result. On replay, it returns the stored result without re-executing the callable.
public function execute(){ // Generate random ID $randomId = yield sideEffect(fn () => random_int(PHP_INT_MIN, PHP_INT_MAX)); // Use in activity yield activity(CreateRecordActivity::class, $randomId); return $randomId;}
Compare deterministic vs non-deterministic execution:
use function Workflow\{activity, sideEffect};class ComparisonWorkflow extends Workflow{ public function execute() { // ✓ Deterministic - cached during replay $goodRandom = yield sideEffect(fn () => random_int(PHP_INT_MIN, PHP_INT_MAX)); // ❌ Non-deterministic - different on replay $badRandom = random_int(PHP_INT_MIN, PHP_INT_MAX); yield activity(ProcessActivity::class); // Pass both to activity $result1 = yield activity(CompareActivity::class, $goodRandom); $result2 = yield activity(CompareActivity::class, $badRandom); if ($goodRandom !== $result1) { throw new \Exception( 'Good random should match because it was wrapped in sideEffect()!' ); } if ($badRandom === $result2) { throw new \Exception( 'Bad random should NOT match because it was not wrapped in sideEffect()!' ); } return 'workflow'; }}
public function execute(){ $id1 = yield sideEffect(fn () => Str::uuid()); $id2 = yield sideEffect(fn () => Str::uuid()); $id3 = yield sideEffect(fn () => Str::uuid()); // All three UUIDs are different and cached yield activity(ProcessActivity::class, $id1, $id2, $id3); // On replay, same three UUIDs are returned return 'completed';}