diff --git a/src/ChildWorkflowStub.php b/src/ChildWorkflowStub.php index e15fe50..530e5d9 100644 --- a/src/ChildWorkflowStub.php +++ b/src/ChildWorkflowStub.php @@ -31,8 +31,21 @@ public static function make($workflow, ...$arguments): PromiseInterface return resolve(Y::unserialize($log->result)); } - WorkflowStub::make($workflow) - ->startAsChild($context->storedWorkflow, $context->index, $context->now, ...$arguments); + $storedChildWorkflow = $context->storedWorkflow->children() + ->wherePivot('parent_index', $context->index) + ->first(); + + $childWorkflow = $storedChildWorkflow ? $storedChildWorkflow->toWorkflow() : WorkflowStub::make($workflow); + + if ($childWorkflow->running() && ! $childWorkflow->created()) { + try { + $childWorkflow->resume(); + } catch (\Spatie\ModelStates\Exceptions\TransitionNotFound) { + // already running + } + } elseif (! $childWorkflow->completed()) { + $childWorkflow->startAsChild($context->storedWorkflow, $context->index, $context->now, ...$arguments); + } ++$context->index; WorkflowStub::setContext($context); diff --git a/src/Models/StoredWorkflow.php b/src/Models/StoredWorkflow.php index 6645174..5a042a0 100644 --- a/src/Models/StoredWorkflow.php +++ b/src/Models/StoredWorkflow.php @@ -66,4 +66,14 @@ public function parents(): \Illuminate\Database\Eloquent\Relations\BelongsToMany 'parent_workflow_id' )->withPivot(['parent_index', 'parent_now']); } + + public function children(): \Illuminate\Database\Eloquent\Relations\BelongsToMany + { + return $this->belongsToMany( + config('workflows.stored_workflow_model', self::class), + 'workflow_relationships', + 'parent_workflow_id', + 'child_workflow_id' + )->withPivot(['parent_index', 'parent_now']); + } } diff --git a/src/WorkflowStub.php b/src/WorkflowStub.php index 196022c..31e4a67 100644 --- a/src/WorkflowStub.php +++ b/src/WorkflowStub.php @@ -10,6 +10,7 @@ use Workflow\Models\StoredWorkflow; use Workflow\Serializers\Y; use Workflow\States\WorkflowCompletedStatus; +use Workflow\States\WorkflowCreatedStatus; use Workflow\States\WorkflowFailedStatus; use Workflow\States\WorkflowPendingStatus; use Workflow\Traits\Awaits; @@ -128,6 +129,21 @@ public function output() return Y::unserialize($this->storedWorkflow->fresh()->output); } + public function completed(): bool + { + return $this->status() === WorkflowCompletedStatus::class; + } + + public function created(): bool + { + return $this->status() === WorkflowCreatedStatus::class; + } + + public function failed(): bool + { + return $this->status() === WorkflowFailedStatus::class; + } + public function running(): bool { return ! in_array($this->status(), [WorkflowCompletedStatus::class, WorkflowFailedStatus::class], true); @@ -146,22 +162,6 @@ public function fresh(): static return $this; } - public function restart(...$arguments): void - { - $this->storedWorkflow->arguments = Y::serialize($arguments); - $this->storedWorkflow->output = null; - $this->storedWorkflow->exceptions() - ->delete(); - $this->storedWorkflow->logs() - ->delete(); - $this->storedWorkflow->signals() - ->delete(); - $this->storedWorkflow->timers() - ->delete(); - - $this->dispatch(); - } - public function resume(): void { $this->dispatch(); @@ -176,7 +176,8 @@ public function start(...$arguments): void public function startAsChild(StoredWorkflow $parentWorkflow, int $index, $now, ...$arguments): void { - $this->storedWorkflow->arguments = Y::serialize($arguments); + $this->storedWorkflow->parents() + ->detach(); $this->storedWorkflow->parents() ->attach($parentWorkflow, [ @@ -184,7 +185,7 @@ public function startAsChild(StoredWorkflow $parentWorkflow, int $index, $now, . 'parent_now' => $now, ]); - $this->dispatch(); + $this->start(...$arguments); } public function fail($exception): void @@ -200,6 +201,16 @@ public function fail($exception): void } $this->storedWorkflow->status->transitionTo(WorkflowFailedStatus::class); + + $this->storedWorkflow->parents() + ->each(static function ($parentWorkflow) use ($exception) { + try { + $parentWorkflow->toWorkflow() + ->fail($exception); + } catch (\Spatie\ModelStates\Exceptions\TransitionNotFound) { + return; + } + }); } public function next($index, $now, $class, $result): void diff --git a/tests/Feature/ParentWorkflowTest.php b/tests/Feature/ParentWorkflowTest.php index f774ebd..02b0913 100644 --- a/tests/Feature/ParentWorkflowTest.php +++ b/tests/Feature/ParentWorkflowTest.php @@ -5,10 +5,13 @@ namespace Tests\Feature; use Tests\Fixtures\TestActivity; +use Tests\Fixtures\TestChildExceptionWorkflow; use Tests\Fixtures\TestChildWorkflow; +use Tests\Fixtures\TestParentExceptionWorkflow; use Tests\Fixtures\TestParentWorkflow; use Tests\TestCase; use Workflow\States\WorkflowCompletedStatus; +use Workflow\States\WorkflowFailedStatus; use Workflow\WorkflowStub; final class ParentWorkflowTest extends TestCase @@ -29,4 +32,29 @@ public function testCompleted(): void ->values() ->toArray()); } + + public function testRetry(): void + { + $workflow = WorkflowStub::make(TestParentExceptionWorkflow::class); + + $workflow->start(shouldThrow: true); + + while ($workflow->running()); + + $this->assertSame(WorkflowFailedStatus::class, $workflow->status()); + $this->assertNull($workflow->output()); + + $workflow->fresh() + ->start(shouldThrow: false); + + while ($workflow->running()); + + $this->assertSame(WorkflowCompletedStatus::class, $workflow->status()); + $this->assertSame('workflow_activity_other', $workflow->output()); + $this->assertSame([TestActivity::class, TestChildExceptionWorkflow::class], $workflow->logs() + ->pluck('class') + ->sort() + ->values() + ->toArray()); + } } diff --git a/tests/Fixtures/TestChildExceptionWorkflow.php b/tests/Fixtures/TestChildExceptionWorkflow.php new file mode 100644 index 0000000..c156d4c --- /dev/null +++ b/tests/Fixtures/TestChildExceptionWorkflow.php @@ -0,0 +1,27 @@ +assertSame('test', $result); } + public function testLoadsChildWorkflow(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestParentWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + $childWorkflow = WorkflowStub::load(WorkflowStub::make(TestChildWorkflow::class)->id()); + $storedChildWorkflow = StoredWorkflow::findOrFail($childWorkflow->id()); + $storedChildWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + $storedChildWorkflow->parents() + ->attach($storedWorkflow, [ + 'parent_index' => 0, + 'parent_now' => now(), + ]); + + $workflow = $storedWorkflow->toWorkflow(); + + $existingChildWorkflow = ChildWorkflowStub::make(TestChildWorkflow::class) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + } + public function testAll(): void { $workflow = WorkflowStub::load(WorkflowStub::make(TestParentWorkflow::class)->id()); diff --git a/tests/Unit/WorkflowStubTest.php b/tests/Unit/WorkflowStubTest.php index 8772504..07d6ea5 100644 --- a/tests/Unit/WorkflowStubTest.php +++ b/tests/Unit/WorkflowStubTest.php @@ -9,10 +9,10 @@ use Tests\Fixtures\TestAwaitWorkflow; use Tests\Fixtures\TestWorkflow; use Tests\TestCase; +use Workflow\Models\StoredWorkflow; use Workflow\Serializers\Y; use Workflow\Signal; use Workflow\States\WorkflowCompletedStatus; -use Workflow\States\WorkflowFailedStatus; use Workflow\States\WorkflowPendingStatus; use Workflow\WorkflowStub; @@ -22,7 +22,15 @@ public function testMake(): void { Carbon::setTestNow('2022-01-01'); + $parentWorkflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedParentWorkflow = StoredWorkflow::findOrFail($parentWorkflow->id()); + $storedParentWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); $workflow->start(); $workflow->cancel(); while (! $workflow->isCanceled()); @@ -33,13 +41,14 @@ public function testMake(): void $this->assertNull($workflow->output()); $this->assertSame(1, $workflow->logs()->count()); + $storedWorkflow->parents() + ->attach($storedParentWorkflow, [ + 'parent_index' => 0, + 'parent_now' => now(), + ]); $workflow->fail(new Exception('test')); - $this->assertSame(WorkflowFailedStatus::class, $workflow->status()); - - $workflow->restart(); - $workflow->fresh(); - $this->assertSame(WorkflowPendingStatus::class, $workflow->status()); - $this->assertSame(0, $workflow->logs()->count()); + $this->assertTrue($workflow->failed()); + $this->assertTrue($parentWorkflow->failed()); $workflow->cancel(); while (! $workflow->isCanceled()); @@ -47,7 +56,7 @@ public function testMake(): void $workflow->fresh(); $this->assertSame(WorkflowPendingStatus::class, $workflow->status()); $this->assertNull($workflow->output()); - $this->assertSame(1, $workflow->logs()->count()); + $this->assertSame(2, $workflow->logs()->count()); } public function testComplete(): void
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: