Skip to content

Commit 23e0322

Browse files
committed
Merged container for nested workflows
1 parent 5256a81 commit 23e0322

4 files changed

Lines changed: 201 additions & 13 deletions

File tree

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,26 +300,32 @@ public function warning(string $message, ?Exception $exception = null): void;
300300
If some of your steps become more complex you may want to have a look into the `NestedWorkflow` wrapper which allows you to use a second workflow as a step of your workflow:
301301

302302
```php
303+
$parentWorkflowContainer = (new \PHPWorkflow\State\WorkflowContainer())->set('parent-data', 'Hello');
304+
$nestedWorkflowContainer = (new \PHPWorkflow\State\WorkflowContainer())->set('nested-data', 'World');
305+
303306
$workflowResult = (new \PHPWorkflow\Workflow('AddSongToPlaylist'))
304307
->validate(new CurrentUserIsAllowedToEditPlaylistValidator())
305308
->before(new \PHPWorkflow\Step\NestedWorkflow(
306309
(new \PHPWorkflow\Workflow('AcceptOpenSuggestions'))
307310
->validate(new PlaylistAcceptsSuggestionsValidator())
308311
->before(new LoadOpenSuggestions())
309312
->process(new AcceptOpenSuggestions())
310-
->onSuccess(new NotifySuggestor())
313+
->onSuccess(new NotifySuggestor()),
314+
$nestedWorkflowContainer,
311315
))
312316
->process(new AddSongToPlaylist())
313317
->onSuccess(new NotifySubscribers())
314-
->executeWorkflow();
318+
->executeWorkflow($parentWorkflowContainer);
315319
```
316320

317321
Each nested workflow must be executable (contain at least one **Process** step).
318322

319323
The debug log of your nested workflow will be embedded in the debug log of your main workflow.
320324

321-
As you can see in the example you can't inject a **WorkflowContainer** into the nested workflow.
322-
The nested workflow has access to your main workflow container and can read or add data to the container.
325+
As you can see in the example you can inject a dedicated **WorkflowContainer** into the nested workflow.
326+
The nested workflow will gain access to a merged **WorkflowContainer** which provides all data and methods of your main workflow container and your nested container.
327+
If you add additional data to the merged container the data will be present in your main workflow container after the nested workflow execution has been completed.
328+
For example your implementations of the steps used in the nested workflow will have access to the keys `nested-data` and `parent-data`.
323329

324330
## Error handling, logging and debugging
325331

src/State/NestedContainer.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PHPWorkflow\State;
6+
7+
class NestedContainer extends WorkflowContainer
8+
{
9+
private WorkflowContainer $parentContainer;
10+
private ?WorkflowContainer $container;
11+
12+
public function __construct(WorkflowContainer $parentContainer, ?WorkflowContainer $container)
13+
{
14+
$this->parentContainer = $parentContainer;
15+
$this->container = $container;
16+
}
17+
18+
public function get(string $key)
19+
{
20+
if (!$this->container) {
21+
return $this->parentContainer->get($key);
22+
}
23+
24+
return $this->container->get($key) ?? $this->parentContainer->get($key);
25+
}
26+
27+
public function set(string $key, $value): WorkflowContainer
28+
{
29+
if ($this->container) {
30+
$this->container->set($key, $value);
31+
}
32+
33+
$this->parentContainer->set($key, $value);
34+
35+
return $this;
36+
}
37+
38+
public function __call(string $name, array $arguments)
39+
{
40+
if ($this->container && method_exists($this->container, $name)) {
41+
return $this->container->{$name}(...$arguments);
42+
}
43+
44+
// don't check if the method exists to trigger an error if the method neither exists in $container nor in
45+
// $parentContainer
46+
return $this->parentContainer->{$name}(...$arguments);
47+
}
48+
}

src/Step/NestedWorkflow.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@
66

77
use PHPWorkflow\Exception\WorkflowException;
88
use PHPWorkflow\ExecutableWorkflow;
9+
use PHPWorkflow\State\NestedContainer;
910
use PHPWorkflow\State\WorkflowContainer;
1011
use PHPWorkflow\WorkflowControl;
1112

1213
class NestedWorkflow implements WorkflowStep
1314
{
1415
private ExecutableWorkflow $nestedWorkflow;
16+
private ?WorkflowContainer $container;
1517

16-
public function __construct(ExecutableWorkflow $nestedWorkflow)
18+
public function __construct(ExecutableWorkflow $nestedWorkflow, ?WorkflowContainer $container = null)
1719
{
1820
$this->nestedWorkflow = $nestedWorkflow;
21+
$this->container = $container;
1922
}
2023

2124
public function getDescription(): string
@@ -27,7 +30,7 @@ public function run(WorkflowControl $control, WorkflowContainer $container)
2730
{
2831
try {
2932
$workflowResult = $this->nestedWorkflow->executeWorkflow(
30-
$container,
33+
new NestedContainer($container, $this->container),
3134
// TODO: array unpacking via named arguments when dropping PHP7 support
3235
$container->get('__internalExecutionConfiguration')['throwOnFailure'],
3336
);

tests/WorkflowTest.php

Lines changed: 138 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use PHPWorkflow\Exception\WorkflowControl\FailWorkflowException;
1010
use PHPWorkflow\Exception\WorkflowException;
1111
use PHPWorkflow\Exception\WorkflowValidationException;
12+
use PHPWorkflow\Middleware\ProfileStep;
1213
use PHPWorkflow\State\WorkflowContainer;
1314
use PHPWorkflow\Step\NestedWorkflow;
1415
use PHPWorkflow\Workflow;
@@ -572,14 +573,18 @@ public function testNestedWorkflow(): void
572573
))
573574
->process($this->setupStep(
574575
'nested-process-test',
575-
fn (WorkflowControl $control, WorkflowContainer $container) =>
576-
$control->attachStepInfo($container->get('testdata')),
576+
function (WorkflowControl $control, WorkflowContainer $container) {
577+
$control->warning('nested-warning-message');
578+
$control->attachStepInfo($container->get('testdata'));
579+
},
577580
))
578581
))
579582
->process($this->setupStep(
580583
'process-test',
581-
fn (WorkflowControl $control, WorkflowContainer $container) =>
582-
$control->attachStepInfo($container->get('additional-data')),
584+
function (WorkflowControl $control, WorkflowContainer $container) {
585+
$control->warning('warning-message');
586+
$control->attachStepInfo($container->get('additional-data'));
587+
},
583588
))
584589
->executeWorkflow($container);
585590

@@ -588,24 +593,29 @@ public function testNestedWorkflow(): void
588593
<<<DEBUG
589594
Process log for workflow 'test':
590595
Before:
591-
- Execute nested workflow: ok
596+
- Execute nested workflow: ok (1 warning)
592597
- Process log for workflow 'nested-test':
593598
Before:
594599
- nested-before-test: ok
595600
Process:
596-
- nested-process-test: ok
601+
- nested-process-test: ok (1 warning)
597602
- Hello World
598603
599604
Summary:
600605
- Workflow execution: ok
601606
- Execution time: *
607+
- Got 1 warning during the execution:
608+
Process: nested-warning-message
602609
Process:
603-
- process-test: ok
610+
- process-test: ok (1 warning)
604611
- from-nested
605612
606613
Summary:
607614
- Workflow execution: ok
608615
- Execution time: *
616+
- Got 2 warnings during the execution:
617+
Before: Nested workflow 'nested-test' emitted 1 warning
618+
Process: warning-message
609619
DEBUG,
610620
$result,
611621
);
@@ -649,4 +659,125 @@ function () {
649659
$result,
650660
);
651661
}
662+
663+
public function testNestedWorkflowHasMergedContainer(): void
664+
{
665+
$parentContainer = new class () extends WorkflowContainer {
666+
public function getParentData(): string
667+
{
668+
return 'parent-data';
669+
}
670+
};
671+
$parentContainer->set('set-parent', 'set-parent-data');
672+
673+
$nestedContainer = new class () extends WorkflowContainer {
674+
public function getNestedData(): string
675+
{
676+
return 'nested-data';
677+
}
678+
};
679+
$nestedContainer->set('set-nested', 'set-nested-data');
680+
681+
682+
$result = (new Workflow('test'))
683+
->before(new NestedWorkflow(
684+
(new Workflow('nested-test'))
685+
->before($this->setupStep(
686+
'nested-before-test',
687+
function (WorkflowControl $control, WorkflowContainer $container) {
688+
$control->attachStepInfo($container->getParentData());
689+
$control->attachStepInfo($container->getNestedData());
690+
$control->attachStepInfo($container->get('set-parent'));
691+
$control->attachStepInfo($container->get('set-nested'));
692+
693+
$container->set('additional-data', 'from-nested');
694+
},
695+
))
696+
->process($this->setupStep(
697+
'nested-process-test',
698+
function (WorkflowControl $control, WorkflowContainer $container) {
699+
$control->attachStepInfo($container->get('additional-data'));
700+
},
701+
)),
702+
$nestedContainer,
703+
))
704+
->process($this->setupStep(
705+
'process-test',
706+
function (WorkflowControl $control, WorkflowContainer $container) {
707+
$control->attachStepInfo($container->get('additional-data'));
708+
$control->attachStepInfo($container->get('set-parent'));
709+
$control->attachStepInfo(var_export($container->get('set-nested'), true));
710+
},
711+
))
712+
->executeWorkflow($parentContainer);
713+
714+
$this->assertTrue($result->success());
715+
$this->assertDebugLog(
716+
<<<DEBUG
717+
Process log for workflow 'test':
718+
Before:
719+
- Execute nested workflow: ok
720+
- Process log for workflow 'nested-test':
721+
Before:
722+
- nested-before-test: ok
723+
- parent-data
724+
- nested-data
725+
- set-parent-data
726+
- set-nested-data
727+
Process:
728+
- nested-process-test: ok
729+
- from-nested
730+
731+
Summary:
732+
- Workflow execution: ok
733+
- Execution time: *
734+
Process:
735+
- process-test: ok
736+
- from-nested
737+
- set-parent-data
738+
- NULL
739+
740+
Summary:
741+
- Workflow execution: ok
742+
- Execution time: *
743+
DEBUG,
744+
$result,
745+
);
746+
747+
// test data is accessible via main container
748+
$this->assertSame('from-nested', $result->getContainer()->get('additional-data'));
749+
$this->assertSame('set-parent-data', $result->getContainer()->get('set-parent'));
750+
$this->assertNull($result->getContainer()->get('set-nested'));
751+
}
752+
753+
public function testProfilingMiddleware(): void
754+
{
755+
$result = (new Workflow('test', new ProfileStep()))
756+
->before($this->setupEmptyStep('before-test'))
757+
->process($this->setupEmptyStep('process-test'))
758+
->onSuccess($this->setupEmptyStep('success-test'))
759+
->executeWorkflow();
760+
761+
$this->assertTrue($result->success());
762+
763+
$this->assertDebugLog(
764+
<<<DEBUG
765+
Process log for workflow 'test':
766+
Before:
767+
- before-test: ok
768+
- Step execution time: *
769+
Process:
770+
- process-test: ok
771+
- Step execution time: *
772+
On Success:
773+
- success-test: ok
774+
- Step execution time: *
775+
776+
Summary:
777+
- Workflow execution: ok
778+
- Execution time: *
779+
DEBUG,
780+
$result,
781+
);
782+
}
652783
}

0 commit comments

Comments
 (0)