From dabc35123dbc220d58de978fe21b4896ac53a7d0 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 08:51:47 +0200 Subject: [PATCH 01/25] Install the symfony/workflow package --- composer.json | 1 + config/packages/workflow.yaml | 2 ++ src/Sylius/Bundle/CoreBundle/composer.json | 1 + .../CoreBundle/test/config/packages/config.yaml | 1 + symfony.lock | 12 ++++++++++++ 5 files changed, 17 insertions(+) create mode 100644 config/packages/workflow.yaml diff --git a/composer.json b/composer.json index 1ffc944596f..57fbdd50b49 100644 --- a/composer.json +++ b/composer.json @@ -134,6 +134,7 @@ "symfony/twig-bundle": "^5.4.21 || ^6.0", "symfony/validator": "^5.4.21 || ^6.0", "symfony/webpack-encore-bundle": "^1.15", + "symfony/workflow": "^5.4.21 || ^6.0", "symfony/yaml": "^5.4.21 || ^6.0", "twig/intl-extra": "^2.12 || ^3.4", "twig/twig": "^2.12 || ^3.3", diff --git a/config/packages/workflow.yaml b/config/packages/workflow.yaml new file mode 100644 index 00000000000..2a716ff0a1b --- /dev/null +++ b/config/packages/workflow.yaml @@ -0,0 +1,2 @@ +framework: + workflows: ~ diff --git a/src/Sylius/Bundle/CoreBundle/composer.json b/src/Sylius/Bundle/CoreBundle/composer.json index eaa6257332c..e5480d6efaf 100644 --- a/src/Sylius/Bundle/CoreBundle/composer.json +++ b/src/Sylius/Bundle/CoreBundle/composer.json @@ -71,6 +71,7 @@ "symfony/messenger": "^5.4.21 || ^6.0", "symfony/templating": "^5.4.21 || ^6.0", "symfony/webpack-encore-bundle": "^1.15", + "symfony/workflow": "^5.4.21 || ^6.0", "winzou/state-machine-bundle": "^0.6" }, "require-dev": { diff --git a/src/Sylius/Bundle/CoreBundle/test/config/packages/config.yaml b/src/Sylius/Bundle/CoreBundle/test/config/packages/config.yaml index dca50202dc8..dcdbff194e8 100644 --- a/src/Sylius/Bundle/CoreBundle/test/config/packages/config.yaml +++ b/src/Sylius/Bundle/CoreBundle/test/config/packages/config.yaml @@ -15,6 +15,7 @@ framework: test: ~ mailer: dsn: 'null://null' + workflows: ~ security: firewalls: diff --git a/symfony.lock b/symfony.lock index bd5a114ac4e..d739fe27398 100644 --- a/symfony.lock +++ b/symfony.lock @@ -937,6 +937,18 @@ "webpack.config.js" ] }, + "symfony/workflow": { + "version": "5.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.3", + "ref": "3b2f8ca32a07fcb00f899649053943fa3d8bbfb6" + }, + "files": [ + "config/packages/workflow.yaml" + ] + }, "symfony/yaml": { "version": "v4.4.13" }, From c3b9c9d76ec0b3b2689ca78c9cf083c936ca3a09 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 09:36:26 +0200 Subject: [PATCH 02/25] Create an interface for Sylius' state machine abstraction --- .../StateMachine/StateMachineInterface.php | 15 +++++++++++++++ .../StateMachine/TransitionInterface.php | 12 ++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php create mode 100644 src/Sylius/Bundle/CoreBundle/StateMachine/TransitionInterface.php diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php b/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php new file mode 100644 index 00000000000..ace165b57bc --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php @@ -0,0 +1,15 @@ + + */ + public function getEnabledTransition(object $subject, string $graphName): array; +} diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/TransitionInterface.php b/src/Sylius/Bundle/CoreBundle/StateMachine/TransitionInterface.php new file mode 100644 index 00000000000..f8aaf0623a1 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/TransitionInterface.php @@ -0,0 +1,12 @@ + Date: Sat, 16 Sep 2023 10:20:25 +0200 Subject: [PATCH 03/25] Add a composite state machine --- .../CoreBundle/Resources/config/services.xml | 1 + .../config/services/state_machines.xml | 25 ++++ .../StateMachine/CompositeStateMachine.php | 74 ++++++++++++ .../CompositeStateMachineSpec.php | 107 ++++++++++++++++++ 4 files changed, 207 insertions(+) create mode 100644 src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml create mode 100644 src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php create mode 100644 src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services.xml index fb40a328529..a1b784e3895 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services.xml @@ -37,6 +37,7 @@ + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml new file mode 100644 index 00000000000..30cfb9642bc --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php new file mode 100644 index 00000000000..c64ec1894d5 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -0,0 +1,74 @@ + */ + private array $stateMachineAdapters; + + public function __construct (iterable $stateMachineAdapters) + { + Assert::allIsInstanceOf($stateMachineAdapters, StateMachineInterface::class); + $this->stateMachineAdapters = $stateMachineAdapters instanceof Traversable ? iterator_to_array($stateMachineAdapters) : $stateMachineAdapters; + } + + /** + * @throws Exception + */ + public function can(object $subject, string $graphName, string $transition): bool + { + $lastException = null; + + foreach ($this->stateMachineAdapters as $stateMachineAdapter) { + try { + return $stateMachineAdapter->can($subject, $graphName, $transition); + } catch (Exception $exception) { + $lastException = $exception; + } + } + + throw $lastException; + } + + /** + * @throws Exception + */ + public function apply(object $subject, string $graphName, string $transition, array $context = []): void + { + $lastException = null; + + foreach ($this->stateMachineAdapters as $stateMachineAdapter) { + try { + $stateMachineAdapter->apply($subject, $graphName, $transition, $context); + return; + } catch (Exception $exception) { + $lastException = $exception; + } + } + + throw $lastException; + } + + /** + * @throws Exception + */ + public function getEnabledTransition(object $subject, string $graphName): array + { + $lastException = null; + + foreach ($this->stateMachineAdapters as $stateMachineAdapter) { + try { + return $stateMachineAdapter->getEnabledTransition($subject, $graphName); + } catch (Exception $exception) { + $lastException = $exception; + } + } + + throw $lastException; + } +} diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php new file mode 100644 index 00000000000..c0f8faa2670 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php @@ -0,0 +1,107 @@ +beConstructedWith([new stdClass()]); + $this->shouldThrow(InvalidArgumentException::class)->duringInstantiation(); + } + + function it_does_not_throw_an_exception_when_all_passed_objects_are_state_machines( + StateMachineInterface $stateMachineOne, + StateMachineInterface $stateMachineTwo, + ): void { + $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); + $this->shouldNotThrow(InvalidArgumentException::class)->duringInstantiation(); + } + + function it_invokes_the_can_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( + StateMachineInterface $stateMachineOne, + StateMachineInterface $stateMachineTwo, + stdClass $subject + ): void { + $stateMachineOne->can($subject, 'graph', 'transition')->willThrow(new Exception()); + $stateMachineTwo->can($subject, 'graph', 'transition')->willReturn(false); + + $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); + $this->can($subject, 'graph', 'transition')->shouldReturn(false); + } + + function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_can_call( + StateMachineInterface $stateMachineOne, + StateMachineInterface $stateMachineTwo, + stdClass $subject + ): void { + $firstException = new Exception(); + $secondException = new Exception(); + + $stateMachineOne->can($subject, 'graph', 'transition')->willThrow($firstException); + $stateMachineTwo->can($subject, 'graph', 'transition')->willThrow($secondException); + + $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); + $this->shouldThrow($secondException)->during('can', [$subject, 'graph', 'transition']); + } + + function it_invokes_the_apply_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( + StateMachineInterface $stateMachineOne, + StateMachineInterface $stateMachineTwo, + stdClass $subject + ): void { + $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow(new Exception()); + $stateMachineTwo->apply($subject, 'graph', 'transition', [])->shouldBeCalled(); + + $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); + $this->apply($subject, 'graph', 'transition'); + } + + function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_an_apply_call( + StateMachineInterface $stateMachineOne, + StateMachineInterface $stateMachineTwo, + stdClass $subject + ): void { + $firstException = new Exception(); + $secondException = new Exception(); + + $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow($firstException); + $stateMachineTwo->apply($subject, 'graph', 'transition', [])->willThrow($secondException); + + $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); + $this->shouldThrow($secondException)->during('apply', [$subject, 'graph', 'transition']); + } + + function it_invokes_the_get_enabled_transition_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( + StateMachineInterface $stateMachineOne, + StateMachineInterface $stateMachineTwo, + stdClass $subject + ): void { + $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow(new Exception()); + $stateMachineTwo->getEnabledTransition($subject, 'graph')->willReturn([]); + + $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); + $this->getEnabledTransition($subject, 'graph')->shouldReturn([]); + } + + function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_get_enabled_transition_call( + StateMachineInterface $stateMachineOne, + StateMachineInterface $stateMachineTwo, + stdClass $subject + ): void { + $firstException = new Exception(); + $secondException = new Exception(); + + $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow($firstException); + $stateMachineTwo->getEnabledTransition($subject, 'graph')->willThrow($secondException); + + $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); + $this->shouldThrow($secondException)->during('getEnabledTransition', [$subject, 'graph']); + } +} From 21a9b6068eb202d9b5cb6436ba076b331de2385e Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 11:04:24 +0200 Subject: [PATCH 04/25] Add a Symfony Workflow state machine adapter --- .../config/services/state_machines.xml | 5 ++ .../StateMachine/SymfonyWorkflowAdapter.php | 39 +++++++++++++ .../CoreBundle/StateMachine/Transition.php | 28 +++++++++ .../StateMachine/TransitionInterface.php | 6 ++ .../SymfonyWorkflowAdapterSpec.php | 57 +++++++++++++++++++ 5 files changed, 135 insertions(+) create mode 100644 src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php create mode 100644 src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php create mode 100644 src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml index 30cfb9642bc..fa3e79e7862 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml @@ -21,5 +21,10 @@ + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php b/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php new file mode 100644 index 00000000000..2071c34d98e --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php @@ -0,0 +1,39 @@ +symfonyWorkflowRegistry->get($subject, $graphName)->can($subject, $transition); + } + + public function apply(object $subject, string $graphName, string $transition, array $context = []): void + { + $this->symfonyWorkflowRegistry->get($subject, $graphName)->apply($subject, $transition, $context); + } + + public function getEnabledTransition(object $subject, string $graphName): array + { + $enabledTransitions = $this->symfonyWorkflowRegistry->get($subject, $graphName)->getEnabledTransitions($subject); + + return array_map( + function (SymfonyWorkflowTransition $transition): TransitionInterface { + return new Transition( + $transition->getName(), + $transition->getFroms(), + $transition->getTos(), + ); + }, + $enabledTransitions, + ); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php b/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php new file mode 100644 index 00000000000..9cd231ae8d2 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php @@ -0,0 +1,28 @@ +name; + } + + public function getFroms(): ?array + { + return $this->froms; + } + + public function getTos(): ?array + { + return $this->tos; + } +} diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/TransitionInterface.php b/src/Sylius/Bundle/CoreBundle/StateMachine/TransitionInterface.php index f8aaf0623a1..552cd84ae41 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/TransitionInterface.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/TransitionInterface.php @@ -6,7 +6,13 @@ interface TransitionInterface { public function getName(): string; + /** + * @return array|null + */ public function getFroms(): ?array; + /** + * @return array|null + */ public function getTos(): ?array; } diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php new file mode 100644 index 00000000000..7975a5ad99a --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php @@ -0,0 +1,57 @@ +beConstructedWith($symfonyWorkflowRegistry); + } + + function it_returns_whether_a_transition_can_be_applied( + Registry $symfonyWorkflowRegistry, + Workflow $someWorkflow, + stdClass $subject, + ): void { + $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); + $someWorkflow->can($subject, 'transition')->shouldBeCalled()->willReturn(true); + + $this->can($subject, 'some_workflow', 'transition')->shouldReturn(true); + } + + function it_applies_a_transition( + Registry $symfonyWorkflowRegistry, + Workflow $someWorkflow, + stdClass $subject, + ): void { + $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); + $someWorkflow->apply($subject, 'transition', [])->shouldBeCalled(); + + $this->apply($subject, 'some_workflow', 'transition'); + } + + function it_returns_enabled_transitions( + Registry $symfonyWorkflowRegistry, + Workflow $someWorkflow, + stdClass $subject, + ): void { + $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); + $someWorkflow->getEnabledTransitions($subject)->shouldBeCalled()->willReturn([ + new SymfonyWorkflowTransition('transition', 'from', 'to'), + new SymfonyWorkflowTransition('transition2', ['from'], ['to']), + ]); + + $this->getEnabledTransition($subject, 'some_workflow')->shouldBeLike([ + new Transition('transition', ['from'], ['to']), + new Transition('transition2', ['from'], ['to']), + ]); + } +} From 5621c84d2f658e19db540f240e2c09927cf4b920 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 12:20:55 +0200 Subject: [PATCH 05/25] Add a WinzouStateMachine state machine adapter --- .../config/services/state_machines.xml | 5 ++ .../WinzouStateMachineAdapter.php | 37 +++++++++++++ .../WinzouStateMachineAdapterSpec.php | 53 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php create mode 100644 src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml index fa3e79e7862..5ac354ef249 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml @@ -26,5 +26,10 @@ + + + + + diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php b/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php new file mode 100644 index 00000000000..9a5aa90c7b4 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php @@ -0,0 +1,37 @@ +getStateMachine($subject, $graphName)->can($transition); + } + + public function apply(object $subject, string $graphName, string $transition, array $context = []): void + { + $this->getStateMachine($subject, $graphName)->apply($transition); + } + + public function getEnabledTransition(object $subject, string $graphName): array + { + $transitions = $this->getStateMachine($subject, $graphName)->getPossibleTransitions(); + + return array_map( + fn (string $transition) => new Transition($transition, null, null), + $transitions + ); + } + + private function getStateMachine(object $subject, string $graphName): \SM\StateMachine\StateMachineInterface + { + return $this->winzouStateMachineFactory->get($subject, $graphName); + } +} diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php new file mode 100644 index 00000000000..f05e71ed991 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php @@ -0,0 +1,53 @@ +beConstructedWith($winzouStateMachineFactory); + } + + function it_returns_whether_a_transition_can_be_applied( + FactoryInterface $winzouStateMachineFactory, + StateMachineInterface $stateMachine, + stdClass $subject, + ): void { + $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); + $stateMachine->can('transition')->shouldBeCalled()->willReturn(true); + + $this->can($subject, 'some_graph', 'transition')->shouldReturn(true); + } + + function it_applies_a_transition( + FactoryInterface $winzouStateMachineFactory, + StateMachineInterface $stateMachine, + stdClass $subject, + ): void { + $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); + $stateMachine->apply('transition')->shouldBeCalled()->willReturn(true); + + $this->apply($subject, 'some_graph', 'transition'); + } + + function it_returns_enabled_transitions( + FactoryInterface $winzouStateMachineFactory, + StateMachineInterface $stateMachine, + stdClass $subject, + ): void { + $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); + $stateMachine->getPossibleTransitions()->shouldBeCalled()->willReturn(['transition', 'transition2']); + + $this->getEnabledTransition($subject, 'some_graph')->shouldBeLike([ + new Transition('transition', null, null), + new Transition('transition2', null, null), + ]); + } +} From bc6aa9d4a491205baac78d22627651f6d1b50133 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 13:04:21 +0200 Subject: [PATCH 06/25] Add missing tags on state machine adapters --- .../CoreBundle/Resources/config/services/state_machines.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml index 5ac354ef249..91f85dbcda6 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml @@ -14,6 +14,8 @@ + + @@ -24,11 +26,15 @@ + + + + From 5db98bb50ba0890aa95b80aa01a29b4976941b3b Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 13:05:08 +0200 Subject: [PATCH 07/25] Creating models for testing the state machine abstraction --- .../test/config/packages/state_machine.yaml | 55 +++++++++++++++++++ .../CoreBundle/test/src/Model/BlogPost.php | 21 +++++++ .../CoreBundle/test/src/Model/Comment.php | 21 +++++++ 3 files changed, 97 insertions(+) create mode 100644 src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml create mode 100644 src/Sylius/Bundle/CoreBundle/test/src/Model/BlogPost.php create mode 100644 src/Sylius/Bundle/CoreBundle/test/src/Model/Comment.php diff --git a/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml b/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml new file mode 100644 index 00000000000..77e64bdf720 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml @@ -0,0 +1,55 @@ +winzou_state_machine: + app_blog_post: + class: Sylius\Bundle\CoreBundle\Application\Model\BlogPost + property_path: state + graph: app_blog_post + state_machine_class: Sylius\Component\Resource\StateMachine\StateMachine + states: + new: ~ + published: ~ + unpublished: ~ + transitions: + publish: + from: [new, unpublished] + to: published + unpublish: + from: [published] + to: unpublished + app_comment: + class: Sylius\Bundle\CoreBundle\Application\Model\Comment + property_path: state + graph: app_comment + state_machine_class: Sylius\Component\Resource\StateMachine\StateMachine + states: + new: ~ + published: ~ + unpublished: ~ + transitions: + publish: + from: [new, unpublished] + to: published + unpublish: + from: [published] + to: unpublished + +framework: + workflows: + app_blog_post: + type: state_machine + marking_store: + type: method + property: state + supports: + - Sylius\Bundle\CoreBundle\Application\Model\BlogPost + initial_marking: new + places: + - new + - published + - unpublished + transitions: + publish: + from: [new, unpublished] + to: published + unpublish: + from: published + to: unpublished diff --git a/src/Sylius/Bundle/CoreBundle/test/src/Model/BlogPost.php b/src/Sylius/Bundle/CoreBundle/test/src/Model/BlogPost.php new file mode 100644 index 00000000000..bb52b1cd4eb --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/test/src/Model/BlogPost.php @@ -0,0 +1,21 @@ +state; + } + + public function setState(string $state): void + { + $this->state = $state; + } +} diff --git a/src/Sylius/Bundle/CoreBundle/test/src/Model/Comment.php b/src/Sylius/Bundle/CoreBundle/test/src/Model/Comment.php new file mode 100644 index 00000000000..6f0c72f9c97 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/test/src/Model/Comment.php @@ -0,0 +1,21 @@ +state; + } + + public function setState(string $state): void + { + $this->state = $state; + } +} From af39f9a03b33cb129c9a73043fe32b3a3632335a Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 13:29:39 +0200 Subject: [PATCH 08/25] fixup! Creating models for testing the state machine abstraction --- .../Bundle/CoreBundle/test/config/packages/state_machine.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml b/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml index 77e64bdf720..e76ed612c6c 100644 --- a/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml +++ b/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml @@ -9,10 +9,10 @@ winzou_state_machine: published: ~ unpublished: ~ transitions: - publish: + post: from: [new, unpublished] to: published - unpublish: + unpost: from: [published] to: unpublished app_comment: From 1f964a36f2537774e550c782a47bdb13fe1f4c02 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 13:29:58 +0200 Subject: [PATCH 09/25] Add functional tests checking whether CompositeStateMachine works correctly --- .../StateMachineCompositeTest.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php diff --git a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php new file mode 100644 index 00000000000..5ecc4e7a401 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php @@ -0,0 +1,51 @@ +getStateMachine(); + + $blogPost = new BlogPost(); + + $this->assertTrue($stateMachine->can($blogPost, 'app_blog_post', 'publish')); + $this->assertFalse($stateMachine->can($blogPost, 'app_blog_post', 'post')); + } + + /** @test */ + public function it_returns_whether_a_transition_can_be_applied_fallback_to_the_state_machine_adapters_with_the_lower_priority(): void + { + $stateMachine = $this->getStateMachine(); + + $comment = new Comment(); + + $this->assertTrue($stateMachine->can($comment, 'app_comment', 'publish')); + } + + /** @test */ + public function it_throws_the_last_exception_thrown_by_the_state_machine_adapters(): void + { + $stateMachine = $this->getStateMachine(); + + $comment = new Comment(); + + $this->expectException(SMException::class); + $this->expectExceptionMessage('Cannot create a state machine because the configuration for object "Sylius\Bundle\CoreBundle\Application\Model\Comment" with graph "app_blog_comment" does not exist'); + + $stateMachine->apply($comment, 'app_blog_comment', 'publish'); + } + + private function getStateMachine(): StateMachineInterface + { + return self::getContainer()->get('sylius.state_machine.composite'); + } +} From 99dc3faf580e78ae7879e637ab49813d7c412283 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 13:33:12 +0200 Subject: [PATCH 10/25] Add checking whether any state machine adapter is passed to the composite --- .../StateMachine/CompositeStateMachine.php | 7 ++++++- .../StateMachine/CompositeStateMachineSpec.php | 18 +++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php index c64ec1894d5..9ae5bd4ba88 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -13,7 +13,12 @@ class CompositeStateMachine implements StateMachineInterface public function __construct (iterable $stateMachineAdapters) { - Assert::allIsInstanceOf($stateMachineAdapters, StateMachineInterface::class); + Assert::notEmpty($stateMachineAdapters, 'At least one state machine adapter should be provided.'); + Assert::allIsInstanceOf( + $stateMachineAdapters, + StateMachineInterface::class, + sprintf('All state machine adapters should implement the "%s" interface.', StateMachineInterface::class) + ); $this->stateMachineAdapters = $stateMachineAdapters instanceof Traversable ? iterator_to_array($stateMachineAdapters) : $stateMachineAdapters; } diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php index c0f8faa2670..cd50cd1f1fd 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php @@ -10,10 +10,26 @@ final class CompositeStateMachineSpec extends ObjectBehavior { + function it_throws_an_exception_when_no_state_machine_is_passed(): void + { + $this->beConstructedWith([]); + $this + ->shouldThrow( + new InvalidArgumentException('At least one state machine adapter should be provided.') + )->duringInstantiation() + ; + } + function it_throws_an_exception_when_any_of_passed_objects_is_not_a_state_machine(): void { $this->beConstructedWith([new stdClass()]); - $this->shouldThrow(InvalidArgumentException::class)->duringInstantiation(); + $this + ->shouldThrow( + new InvalidArgumentException( + sprintf('All state machine adapters should implement the "%s" interface.', StateMachineInterface::class) + ), + )->duringInstantiation() + ; } function it_does_not_throw_an_exception_when_all_passed_objects_are_state_machines( From 2d643dc5ed9de42e5dcd13d7939f9b509570637e Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 13:35:45 +0200 Subject: [PATCH 11/25] Apply ECS fixes --- .../StateMachine/CompositeStateMachine.php | 16 +++++++++-- .../StateMachine/StateMachineInterface.php | 11 ++++++++ .../StateMachine/SymfonyWorkflowAdapter.php | 13 ++++++++- .../CoreBundle/StateMachine/Transition.php | 13 ++++++++- .../StateMachine/TransitionInterface.php | 11 ++++++++ .../WinzouStateMachineAdapter.php | 15 +++++++++-- .../StateMachineCompositeTest.php | 11 ++++++++ .../CompositeStateMachineSpec.php | 27 +++++++++++++------ .../SymfonyWorkflowAdapterSpec.php | 11 ++++++++ .../WinzouStateMachineAdapterSpec.php | 11 ++++++++ .../CoreBundle/test/src/Model/BlogPost.php | 13 ++++++++- .../CoreBundle/test/src/Model/Comment.php | 13 ++++++++- 12 files changed, 149 insertions(+), 16 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php index 9ae5bd4ba88..8328dbc3f91 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -1,5 +1,16 @@ */ private array $stateMachineAdapters; - public function __construct (iterable $stateMachineAdapters) + public function __construct(iterable $stateMachineAdapters) { Assert::notEmpty($stateMachineAdapters, 'At least one state machine adapter should be provided.'); Assert::allIsInstanceOf( $stateMachineAdapters, StateMachineInterface::class, - sprintf('All state machine adapters should implement the "%s" interface.', StateMachineInterface::class) + sprintf('All state machine adapters should implement the "%s" interface.', StateMachineInterface::class), ); $this->stateMachineAdapters = $stateMachineAdapters instanceof Traversable ? iterator_to_array($stateMachineAdapters) : $stateMachineAdapters; } @@ -50,6 +61,7 @@ public function apply(object $subject, string $graphName, string $transition, ar foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { $stateMachineAdapter->apply($subject, $graphName, $transition, $context); + return; } catch (Exception $exception) { $lastException = $exception; diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php b/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php index ace165b57bc..824633f1bfd 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php @@ -1,5 +1,16 @@ new Transition($transition, null, null), - $transitions + $transitions, ); } diff --git a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php index 5ecc4e7a401..20c3ca09338 100644 --- a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php +++ b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php @@ -1,5 +1,16 @@ beConstructedWith([]); $this ->shouldThrow( - new InvalidArgumentException('At least one state machine adapter should be provided.') + new InvalidArgumentException('At least one state machine adapter should be provided.'), )->duringInstantiation() ; } @@ -26,7 +37,7 @@ function it_throws_an_exception_when_any_of_passed_objects_is_not_a_state_machin $this ->shouldThrow( new InvalidArgumentException( - sprintf('All state machine adapters should implement the "%s" interface.', StateMachineInterface::class) + sprintf('All state machine adapters should implement the "%s" interface.', StateMachineInterface::class), ), )->duringInstantiation() ; @@ -43,7 +54,7 @@ function it_does_not_throw_an_exception_when_all_passed_objects_are_state_machin function it_invokes_the_can_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject + stdClass $subject, ): void { $stateMachineOne->can($subject, 'graph', 'transition')->willThrow(new Exception()); $stateMachineTwo->can($subject, 'graph', 'transition')->willReturn(false); @@ -55,7 +66,7 @@ function it_invokes_the_can_method_on_a_first_state_machine_with_a_configured_gr function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_can_call( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject + stdClass $subject, ): void { $firstException = new Exception(); $secondException = new Exception(); @@ -70,7 +81,7 @@ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured function it_invokes_the_apply_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject + stdClass $subject, ): void { $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow(new Exception()); $stateMachineTwo->apply($subject, 'graph', 'transition', [])->shouldBeCalled(); @@ -82,7 +93,7 @@ function it_invokes_the_apply_method_on_a_first_state_machine_with_a_configured_ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_an_apply_call( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject + stdClass $subject, ): void { $firstException = new Exception(); $secondException = new Exception(); @@ -97,7 +108,7 @@ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured function it_invokes_the_get_enabled_transition_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject + stdClass $subject, ): void { $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow(new Exception()); $stateMachineTwo->getEnabledTransition($subject, 'graph')->willReturn([]); @@ -109,7 +120,7 @@ function it_invokes_the_get_enabled_transition_method_on_a_first_state_machine_w function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_get_enabled_transition_call( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject + stdClass $subject, ): void { $firstException = new Exception(); $secondException = new Exception(); diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php index 7975a5ad99a..4a52835c779 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php @@ -1,5 +1,16 @@ Date: Sat, 16 Sep 2023 16:07:11 +0200 Subject: [PATCH 12/25] Remove `use` statements for the classes from the global namespace --- .../StateMachine/CompositeStateMachine.php | 13 ++++--- .../CompositeStateMachineSpec.php | 34 +++++++++---------- .../SymfonyWorkflowAdapterSpec.php | 7 ++-- .../WinzouStateMachineAdapterSpec.php | 7 ++-- 4 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php index 8328dbc3f91..b03daeacb2a 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -13,7 +13,6 @@ namespace Sylius\Bundle\CoreBundle\StateMachine; -use Exception; use Traversable; use Webmozart\Assert\Assert; @@ -34,7 +33,7 @@ public function __construct(iterable $stateMachineAdapters) } /** - * @throws Exception + * @throws \Exception */ public function can(object $subject, string $graphName, string $transition): bool { @@ -43,7 +42,7 @@ public function can(object $subject, string $graphName, string $transition): boo foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { return $stateMachineAdapter->can($subject, $graphName, $transition); - } catch (Exception $exception) { + } catch (\Exception $exception) { $lastException = $exception; } } @@ -52,7 +51,7 @@ public function can(object $subject, string $graphName, string $transition): boo } /** - * @throws Exception + * @throws \Exception */ public function apply(object $subject, string $graphName, string $transition, array $context = []): void { @@ -63,7 +62,7 @@ public function apply(object $subject, string $graphName, string $transition, ar $stateMachineAdapter->apply($subject, $graphName, $transition, $context); return; - } catch (Exception $exception) { + } catch (\Exception $exception) { $lastException = $exception; } } @@ -72,7 +71,7 @@ public function apply(object $subject, string $graphName, string $transition, ar } /** - * @throws Exception + * @throws \Exception */ public function getEnabledTransition(object $subject, string $graphName): array { @@ -81,7 +80,7 @@ public function getEnabledTransition(object $subject, string $graphName): array foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { return $stateMachineAdapter->getEnabledTransition($subject, $graphName); - } catch (Exception $exception) { + } catch (\Exception $exception) { $lastException = $exception; } } diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php index da8f11acb3d..84a6de9751d 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php @@ -13,9 +13,7 @@ namespace spec\Sylius\Bundle\CoreBundle\StateMachine; -use Exception; use PhpSpec\ObjectBehavior; -use stdClass; use Sylius\Bundle\CoreBundle\StateMachine\StateMachineInterface; use Webmozart\Assert\InvalidArgumentException; @@ -33,7 +31,7 @@ function it_throws_an_exception_when_no_state_machine_is_passed(): void function it_throws_an_exception_when_any_of_passed_objects_is_not_a_state_machine(): void { - $this->beConstructedWith([new stdClass()]); + $this->beConstructedWith([new \stdClass()]); $this ->shouldThrow( new InvalidArgumentException( @@ -54,9 +52,9 @@ function it_does_not_throw_an_exception_when_all_passed_objects_are_state_machin function it_invokes_the_can_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject, + \stdClass $subject, ): void { - $stateMachineOne->can($subject, 'graph', 'transition')->willThrow(new Exception()); + $stateMachineOne->can($subject, 'graph', 'transition')->willThrow(new \Exception()); $stateMachineTwo->can($subject, 'graph', 'transition')->willReturn(false); $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); @@ -66,10 +64,10 @@ function it_invokes_the_can_method_on_a_first_state_machine_with_a_configured_gr function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_can_call( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject, + \stdClass $subject, ): void { - $firstException = new Exception(); - $secondException = new Exception(); + $firstException = new \Exception(); + $secondException = new \Exception(); $stateMachineOne->can($subject, 'graph', 'transition')->willThrow($firstException); $stateMachineTwo->can($subject, 'graph', 'transition')->willThrow($secondException); @@ -81,9 +79,9 @@ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured function it_invokes_the_apply_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject, + \stdClass $subject, ): void { - $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow(new Exception()); + $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow(new \Exception()); $stateMachineTwo->apply($subject, 'graph', 'transition', [])->shouldBeCalled(); $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); @@ -93,10 +91,10 @@ function it_invokes_the_apply_method_on_a_first_state_machine_with_a_configured_ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_an_apply_call( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject, + \stdClass $subject, ): void { - $firstException = new Exception(); - $secondException = new Exception(); + $firstException = new \Exception(); + $secondException = new \Exception(); $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow($firstException); $stateMachineTwo->apply($subject, 'graph', 'transition', [])->willThrow($secondException); @@ -108,9 +106,9 @@ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured function it_invokes_the_get_enabled_transition_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject, + \stdClass $subject, ): void { - $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow(new Exception()); + $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow(new \Exception()); $stateMachineTwo->getEnabledTransition($subject, 'graph')->willReturn([]); $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); @@ -120,10 +118,10 @@ function it_invokes_the_get_enabled_transition_method_on_a_first_state_machine_w function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_get_enabled_transition_call( StateMachineInterface $stateMachineOne, StateMachineInterface $stateMachineTwo, - stdClass $subject, + \stdClass $subject, ): void { - $firstException = new Exception(); - $secondException = new Exception(); + $firstException = new \Exception(); + $secondException = new \Exception(); $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow($firstException); $stateMachineTwo->getEnabledTransition($subject, 'graph')->willThrow($secondException); diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php index 4a52835c779..22f166ee8ac 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php @@ -14,7 +14,6 @@ namespace spec\Sylius\Bundle\CoreBundle\StateMachine; use PhpSpec\ObjectBehavior; -use stdClass; use Sylius\Bundle\CoreBundle\StateMachine\Transition; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Transition as SymfonyWorkflowTransition; @@ -30,7 +29,7 @@ function let(Registry $symfonyWorkflowRegistry): void function it_returns_whether_a_transition_can_be_applied( Registry $symfonyWorkflowRegistry, Workflow $someWorkflow, - stdClass $subject, + \stdClass $subject, ): void { $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); $someWorkflow->can($subject, 'transition')->shouldBeCalled()->willReturn(true); @@ -41,7 +40,7 @@ function it_returns_whether_a_transition_can_be_applied( function it_applies_a_transition( Registry $symfonyWorkflowRegistry, Workflow $someWorkflow, - stdClass $subject, + \stdClass $subject, ): void { $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); $someWorkflow->apply($subject, 'transition', [])->shouldBeCalled(); @@ -52,7 +51,7 @@ function it_applies_a_transition( function it_returns_enabled_transitions( Registry $symfonyWorkflowRegistry, Workflow $someWorkflow, - stdClass $subject, + \stdClass $subject, ): void { $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); $someWorkflow->getEnabledTransitions($subject)->shouldBeCalled()->willReturn([ diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php index 41511088b1b..8d1276b1ab4 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php @@ -16,7 +16,6 @@ use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface; use SM\StateMachine\StateMachineInterface; -use stdClass; use Sylius\Bundle\CoreBundle\StateMachine\Transition; final class WinzouStateMachineAdapterSpec extends ObjectBehavior @@ -29,7 +28,7 @@ function let(FactoryInterface $winzouStateMachineFactory): void function it_returns_whether_a_transition_can_be_applied( FactoryInterface $winzouStateMachineFactory, StateMachineInterface $stateMachine, - stdClass $subject, + \stdClass $subject, ): void { $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); $stateMachine->can('transition')->shouldBeCalled()->willReturn(true); @@ -40,7 +39,7 @@ function it_returns_whether_a_transition_can_be_applied( function it_applies_a_transition( FactoryInterface $winzouStateMachineFactory, StateMachineInterface $stateMachine, - stdClass $subject, + \stdClass $subject, ): void { $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); $stateMachine->apply('transition')->shouldBeCalled()->willReturn(true); @@ -51,7 +50,7 @@ function it_applies_a_transition( function it_returns_enabled_transitions( FactoryInterface $winzouStateMachineFactory, StateMachineInterface $stateMachine, - stdClass $subject, + \stdClass $subject, ): void { $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); $stateMachine->getPossibleTransitions()->shouldBeCalled()->willReturn(['transition', 'transition2']); From 24a7ab02033bed52923978e5df716dbcbb34bb5e Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 16:12:54 +0200 Subject: [PATCH 13/25] Replace the null value for $lastException with an empty exception --- .../CoreBundle/StateMachine/CompositeStateMachine.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php index b03daeacb2a..7685fc11176 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -37,7 +37,7 @@ public function __construct(iterable $stateMachineAdapters) */ public function can(object $subject, string $graphName, string $transition): bool { - $lastException = null; + $lastException = new \Exception(); foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { @@ -55,7 +55,7 @@ public function can(object $subject, string $graphName, string $transition): boo */ public function apply(object $subject, string $graphName, string $transition, array $context = []): void { - $lastException = null; + $lastException = new \Exception(); foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { @@ -75,7 +75,7 @@ public function apply(object $subject, string $graphName, string $transition, ar */ public function getEnabledTransition(object $subject, string $graphName): array { - $lastException = null; + $lastException = new \Exception(); foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { From b6a9c621f99dfed95b0ee8a39abf5d9991a8c931 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 16:19:54 +0200 Subject: [PATCH 14/25] Improve handling of exceptions in the composite state machine --- .../StateMachine/CompositeStateMachine.php | 7 ++++--- .../StateMachineExecutionException.php | 7 +++++++ .../StateMachine/StateMachineInterface.php | 9 +++++++++ .../CompositeStateMachineSpec.php | 19 ++++++++++--------- 4 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 src/Sylius/Bundle/CoreBundle/StateMachine/Exception/StateMachineExecutionException.php diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php index 7685fc11176..7f67ff0de05 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -13,6 +13,7 @@ namespace Sylius\Bundle\CoreBundle\StateMachine; +use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; use Traversable; use Webmozart\Assert\Assert; @@ -42,7 +43,7 @@ public function can(object $subject, string $graphName, string $transition): boo foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { return $stateMachineAdapter->can($subject, $graphName, $transition); - } catch (\Exception $exception) { + } catch (StateMachineExecutionException $exception) { $lastException = $exception; } } @@ -62,7 +63,7 @@ public function apply(object $subject, string $graphName, string $transition, ar $stateMachineAdapter->apply($subject, $graphName, $transition, $context); return; - } catch (\Exception $exception) { + } catch (StateMachineExecutionException $exception) { $lastException = $exception; } } @@ -80,7 +81,7 @@ public function getEnabledTransition(object $subject, string $graphName): array foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { return $stateMachineAdapter->getEnabledTransition($subject, $graphName); - } catch (\Exception $exception) { + } catch (StateMachineExecutionException $exception) { $lastException = $exception; } } diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/Exception/StateMachineExecutionException.php b/src/Sylius/Bundle/CoreBundle/StateMachine/Exception/StateMachineExecutionException.php new file mode 100644 index 00000000000..fbb220ac4f0 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/Exception/StateMachineExecutionException.php @@ -0,0 +1,7 @@ + */ public function getEnabledTransition(object $subject, string $graphName): array; diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php index 84a6de9751d..a480abaf419 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php @@ -14,6 +14,7 @@ namespace spec\Sylius\Bundle\CoreBundle\StateMachine; use PhpSpec\ObjectBehavior; +use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; use Sylius\Bundle\CoreBundle\StateMachine\StateMachineInterface; use Webmozart\Assert\InvalidArgumentException; @@ -54,7 +55,7 @@ function it_invokes_the_can_method_on_a_first_state_machine_with_a_configured_gr StateMachineInterface $stateMachineTwo, \stdClass $subject, ): void { - $stateMachineOne->can($subject, 'graph', 'transition')->willThrow(new \Exception()); + $stateMachineOne->can($subject, 'graph', 'transition')->willThrow(new StateMachineExecutionException()); $stateMachineTwo->can($subject, 'graph', 'transition')->willReturn(false); $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); @@ -66,8 +67,8 @@ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured StateMachineInterface $stateMachineTwo, \stdClass $subject, ): void { - $firstException = new \Exception(); - $secondException = new \Exception(); + $firstException = new StateMachineExecutionException(); + $secondException = new StateMachineExecutionException(); $stateMachineOne->can($subject, 'graph', 'transition')->willThrow($firstException); $stateMachineTwo->can($subject, 'graph', 'transition')->willThrow($secondException); @@ -81,7 +82,7 @@ function it_invokes_the_apply_method_on_a_first_state_machine_with_a_configured_ StateMachineInterface $stateMachineTwo, \stdClass $subject, ): void { - $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow(new \Exception()); + $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow(new StateMachineExecutionException()); $stateMachineTwo->apply($subject, 'graph', 'transition', [])->shouldBeCalled(); $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); @@ -93,8 +94,8 @@ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured StateMachineInterface $stateMachineTwo, \stdClass $subject, ): void { - $firstException = new \Exception(); - $secondException = new \Exception(); + $firstException = new StateMachineExecutionException(); + $secondException = new StateMachineExecutionException(); $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow($firstException); $stateMachineTwo->apply($subject, 'graph', 'transition', [])->willThrow($secondException); @@ -108,7 +109,7 @@ function it_invokes_the_get_enabled_transition_method_on_a_first_state_machine_w StateMachineInterface $stateMachineTwo, \stdClass $subject, ): void { - $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow(new \Exception()); + $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow(new StateMachineExecutionException()); $stateMachineTwo->getEnabledTransition($subject, 'graph')->willReturn([]); $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); @@ -120,8 +121,8 @@ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured StateMachineInterface $stateMachineTwo, \stdClass $subject, ): void { - $firstException = new \Exception(); - $secondException = new \Exception(); + $firstException = new StateMachineExecutionException(); + $secondException = new StateMachineExecutionException(); $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow($firstException); $stateMachineTwo->getEnabledTransition($subject, 'graph')->willThrow($secondException); From 67ef56f924d8995ec51101d9fcf779bee1e61acb Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 16:22:21 +0200 Subject: [PATCH 15/25] Add translating of Symfony Workflow's exceptions to the abstracted one --- .../StateMachine/SymfonyWorkflowAdapter.php | 20 +++++++++-- .../SymfonyWorkflowAdapterSpec.php | 35 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php b/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php index 8720ba991fc..fb289c59470 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php @@ -13,6 +13,8 @@ namespace Sylius\Bundle\CoreBundle\StateMachine; +use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; +use Symfony\Component\Workflow\Exception\InvalidArgumentException; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Transition as SymfonyWorkflowTransition; @@ -24,17 +26,29 @@ public function __construct(private Registry $symfonyWorkflowRegistry) public function can(object $subject, string $graphName, string $transition): bool { - return $this->symfonyWorkflowRegistry->get($subject, $graphName)->can($subject, $transition); + try { + return $this->symfonyWorkflowRegistry->get($subject, $graphName)->can($subject, $transition); + } catch (InvalidArgumentException $exception) { + throw new StateMachineExecutionException($exception->getMessage(), $exception->getCode(), $exception); + } } public function apply(object $subject, string $graphName, string $transition, array $context = []): void { - $this->symfonyWorkflowRegistry->get($subject, $graphName)->apply($subject, $transition, $context); + try { + $this->symfonyWorkflowRegistry->get($subject, $graphName)->apply($subject, $transition, $context); + } catch (InvalidArgumentException $exception) { + throw new StateMachineExecutionException($exception->getMessage(), $exception->getCode(), $exception); + } } public function getEnabledTransition(object $subject, string $graphName): array { - $enabledTransitions = $this->symfonyWorkflowRegistry->get($subject, $graphName)->getEnabledTransitions($subject); + try { + $enabledTransitions = $this->symfonyWorkflowRegistry->get($subject, $graphName)->getEnabledTransitions($subject); + } catch (InvalidArgumentException $exception) { + throw new StateMachineExecutionException($exception->getMessage(), $exception->getCode(), $exception); + } return array_map( function (SymfonyWorkflowTransition $transition): TransitionInterface { diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php index 22f166ee8ac..fd1d23540dc 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php @@ -14,7 +14,9 @@ namespace spec\Sylius\Bundle\CoreBundle\StateMachine; use PhpSpec\ObjectBehavior; +use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; use Sylius\Bundle\CoreBundle\StateMachine\Transition; +use Symfony\Component\Workflow\Exception\InvalidArgumentException; use Symfony\Component\Workflow\Registry; use Symfony\Component\Workflow\Transition as SymfonyWorkflowTransition; use Symfony\Component\Workflow\Workflow; @@ -37,6 +39,17 @@ function it_returns_whether_a_transition_can_be_applied( $this->can($subject, 'some_workflow', 'transition')->shouldReturn(true); } + function it_translates_invalid_argument_exception_to_state_machine_execution_exception_on_the_can_method_call( + Registry $symfonyWorkflowRegistry, + Workflow $someWorkflow, + \stdClass $subject, + ): void { + $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); + $someWorkflow->can($subject, 'transition')->willThrow(new InvalidArgumentException('Invalid argument')); + + $this->shouldThrow(StateMachineExecutionException::class)->during('can', [$subject, 'some_workflow', 'transition']); + } + function it_applies_a_transition( Registry $symfonyWorkflowRegistry, Workflow $someWorkflow, @@ -48,6 +61,17 @@ function it_applies_a_transition( $this->apply($subject, 'some_workflow', 'transition'); } + function it_translates_invalid_argument_exception_to_state_machine_execution_exception_on_the_apply_method_call( + Registry $symfonyWorkflowRegistry, + Workflow $someWorkflow, + \stdClass $subject, + ): void { + $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); + $someWorkflow->apply($subject, 'transition', [])->willThrow(new InvalidArgumentException('Invalid argument')); + + $this->shouldThrow(StateMachineExecutionException::class)->during('apply', [$subject, 'some_workflow', 'transition']); + } + function it_returns_enabled_transitions( Registry $symfonyWorkflowRegistry, Workflow $someWorkflow, @@ -64,4 +88,15 @@ function it_returns_enabled_transitions( new Transition('transition2', ['from'], ['to']), ]); } + + function it_translates_invalid_argument_exception_to_state_machine_execution_exception_on_the_get_enabled_transition_method_call( + Registry $symfonyWorkflowRegistry, + Workflow $someWorkflow, + \stdClass $subject, + ): void { + $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); + $someWorkflow->getEnabledTransitions($subject)->willThrow(new InvalidArgumentException('Invalid argument')); + + $this->shouldThrow(StateMachineExecutionException::class)->during('getEnabledTransition', [$subject, 'some_workflow']); + } } From c1449144a8b322835f00c120a37bb818fb0755ec Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 16:27:14 +0200 Subject: [PATCH 16/25] Add translating of WinzouStateMachine's exceptions to the abstracted one --- .../WinzouStateMachineAdapter.php | 20 +++++++++-- .../WinzouStateMachineAdapterSpec.php | 35 +++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php b/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php index 6a19b2dc097..f23c46b43df 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php @@ -14,6 +14,8 @@ namespace Sylius\Bundle\CoreBundle\StateMachine; use SM\Factory\FactoryInterface; +use SM\SMException; +use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; final class WinzouStateMachineAdapter implements StateMachineInterface { @@ -23,17 +25,29 @@ public function __construct(private FactoryInterface $winzouStateMachineFactory) public function can(object $subject, string $graphName, string $transition): bool { - return $this->getStateMachine($subject, $graphName)->can($transition); + try { + return $this->getStateMachine($subject, $graphName)->can($transition); + } catch (SMException $exception) { + throw new StateMachineExecutionException($exception->getMessage(), $exception->getCode(), $exception); + } } public function apply(object $subject, string $graphName, string $transition, array $context = []): void { - $this->getStateMachine($subject, $graphName)->apply($transition); + try { + $this->getStateMachine($subject, $graphName)->apply($transition); + } catch (SMException $exception) { + throw new StateMachineExecutionException($exception->getMessage(), $exception->getCode(), $exception); + } } public function getEnabledTransition(object $subject, string $graphName): array { - $transitions = $this->getStateMachine($subject, $graphName)->getPossibleTransitions(); + try { + $transitions = $this->getStateMachine($subject, $graphName)->getPossibleTransitions(); + } catch (SMException $exception) { + throw new StateMachineExecutionException($exception->getMessage(), $exception->getCode(), $exception); + } return array_map( fn (string $transition) => new Transition($transition, null, null), diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php index 8d1276b1ab4..c1378a0e27c 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php @@ -15,7 +15,9 @@ use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface; +use SM\SMException; use SM\StateMachine\StateMachineInterface; +use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; use Sylius\Bundle\CoreBundle\StateMachine\Transition; final class WinzouStateMachineAdapterSpec extends ObjectBehavior @@ -36,6 +38,17 @@ function it_returns_whether_a_transition_can_be_applied( $this->can($subject, 'some_graph', 'transition')->shouldReturn(true); } + function it_translates_winzou_state_machines_exceptions_to_state_machine_execution_exception_on_the_can_method_call( + FactoryInterface $winzouStateMachineFactory, + StateMachineInterface $stateMachine, + \stdClass $subject, + ): void { + $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); + $stateMachine->can('transition')->willThrow(new SMException('Invalid argument')); + + $this->shouldThrow(StateMachineExecutionException::class)->during('can', [$subject, 'some_graph', 'transition']); + } + function it_applies_a_transition( FactoryInterface $winzouStateMachineFactory, StateMachineInterface $stateMachine, @@ -47,6 +60,17 @@ function it_applies_a_transition( $this->apply($subject, 'some_graph', 'transition'); } + function it_translates_winzou_state_machines_exceptions_to_state_machine_execution_exception_on_the_apply_method_call( + FactoryInterface $winzouStateMachineFactory, + StateMachineInterface $stateMachine, + \stdClass $subject, + ): void { + $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); + $stateMachine->apply('transition')->willThrow(new SMException('Invalid argument')); + + $this->shouldThrow(StateMachineExecutionException::class)->during('apply', [$subject, 'some_graph', 'transition']); + } + function it_returns_enabled_transitions( FactoryInterface $winzouStateMachineFactory, StateMachineInterface $stateMachine, @@ -60,4 +84,15 @@ function it_returns_enabled_transitions( new Transition('transition2', null, null), ]); } + + function it_translates_winzou_state_machines_exceptions_to_state_machine_execution_exception_on_the_get_enabled_transition_method_call( + FactoryInterface $winzouStateMachineFactory, + StateMachineInterface $stateMachine, + \stdClass $subject, + ): void { + $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); + $stateMachine->getPossibleTransitions()->willThrow(new SMException('Invalid argument')); + + $this->shouldThrow(StateMachineExecutionException::class)->during('getEnabledTransition', [$subject, 'some_graph']); + } } From 9e4dff023d1b4d76bc5e0fe852011eea4f12b25d Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 16:33:41 +0200 Subject: [PATCH 17/25] Rename the `getEnabledTransition` method to `getEnabledTransitions` --- .../StateMachine/CompositeStateMachine.php | 4 ++-- .../StateMachine/StateMachineInterface.php | 2 +- .../StateMachine/SymfonyWorkflowAdapter.php | 2 +- .../StateMachine/WinzouStateMachineAdapter.php | 2 +- .../spec/StateMachine/CompositeStateMachineSpec.php | 12 ++++++------ .../spec/StateMachine/SymfonyWorkflowAdapterSpec.php | 4 ++-- .../StateMachine/WinzouStateMachineAdapterSpec.php | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php index 7f67ff0de05..1fc977462cb 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -74,13 +74,13 @@ public function apply(object $subject, string $graphName, string $transition, ar /** * @throws \Exception */ - public function getEnabledTransition(object $subject, string $graphName): array + public function getEnabledTransitions(object $subject, string $graphName): array { $lastException = new \Exception(); foreach ($this->stateMachineAdapters as $stateMachineAdapter) { try { - return $stateMachineAdapter->getEnabledTransition($subject, $graphName); + return $stateMachineAdapter->getEnabledTransitions($subject, $graphName); } catch (StateMachineExecutionException $exception) { $lastException = $exception; } diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php b/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php index 2a9979ee15c..c9f365d2961 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php @@ -31,5 +31,5 @@ public function apply(object $subject, string $graphName, string $transition, ar * @throws StateMachineExecutionException * @return array */ - public function getEnabledTransition(object $subject, string $graphName): array; + public function getEnabledTransitions(object $subject, string $graphName): array; } diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php b/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php index fb289c59470..6b020d7c53e 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/SymfonyWorkflowAdapter.php @@ -42,7 +42,7 @@ public function apply(object $subject, string $graphName, string $transition, ar } } - public function getEnabledTransition(object $subject, string $graphName): array + public function getEnabledTransitions(object $subject, string $graphName): array { try { $enabledTransitions = $this->symfonyWorkflowRegistry->get($subject, $graphName)->getEnabledTransitions($subject); diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php b/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php index f23c46b43df..54589bdca2c 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/WinzouStateMachineAdapter.php @@ -41,7 +41,7 @@ public function apply(object $subject, string $graphName, string $transition, ar } } - public function getEnabledTransition(object $subject, string $graphName): array + public function getEnabledTransitions(object $subject, string $graphName): array { try { $transitions = $this->getStateMachine($subject, $graphName)->getPossibleTransitions(); diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php index a480abaf419..7593ff01a66 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php @@ -109,11 +109,11 @@ function it_invokes_the_get_enabled_transition_method_on_a_first_state_machine_w StateMachineInterface $stateMachineTwo, \stdClass $subject, ): void { - $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow(new StateMachineExecutionException()); - $stateMachineTwo->getEnabledTransition($subject, 'graph')->willReturn([]); + $stateMachineOne->getEnabledTransitions($subject, 'graph')->willThrow(new StateMachineExecutionException()); + $stateMachineTwo->getEnabledTransitions($subject, 'graph')->willReturn([]); $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->getEnabledTransition($subject, 'graph')->shouldReturn([]); + $this->getEnabledTransitions($subject, 'graph')->shouldReturn([]); } function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_get_enabled_transition_call( @@ -124,10 +124,10 @@ function it_throws_the_last_caught_exception_when_no_state_machine_is_configured $firstException = new StateMachineExecutionException(); $secondException = new StateMachineExecutionException(); - $stateMachineOne->getEnabledTransition($subject, 'graph')->willThrow($firstException); - $stateMachineTwo->getEnabledTransition($subject, 'graph')->willThrow($secondException); + $stateMachineOne->getEnabledTransitions($subject, 'graph')->willThrow($firstException); + $stateMachineTwo->getEnabledTransitions($subject, 'graph')->willThrow($secondException); $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->shouldThrow($secondException)->during('getEnabledTransition', [$subject, 'graph']); + $this->shouldThrow($secondException)->during('getEnabledTransitions', [$subject, 'graph']); } } diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php index fd1d23540dc..9ed55c7ee23 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/SymfonyWorkflowAdapterSpec.php @@ -83,7 +83,7 @@ function it_returns_enabled_transitions( new SymfonyWorkflowTransition('transition2', ['from'], ['to']), ]); - $this->getEnabledTransition($subject, 'some_workflow')->shouldBeLike([ + $this->getEnabledTransitions($subject, 'some_workflow')->shouldBeLike([ new Transition('transition', ['from'], ['to']), new Transition('transition2', ['from'], ['to']), ]); @@ -97,6 +97,6 @@ function it_translates_invalid_argument_exception_to_state_machine_execution_exc $symfonyWorkflowRegistry->get($subject, 'some_workflow')->willReturn($someWorkflow); $someWorkflow->getEnabledTransitions($subject)->willThrow(new InvalidArgumentException('Invalid argument')); - $this->shouldThrow(StateMachineExecutionException::class)->during('getEnabledTransition', [$subject, 'some_workflow']); + $this->shouldThrow(StateMachineExecutionException::class)->during('getEnabledTransitions', [$subject, 'some_workflow']); } } diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php index c1378a0e27c..07f4e0ab2a5 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/WinzouStateMachineAdapterSpec.php @@ -79,7 +79,7 @@ function it_returns_enabled_transitions( $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); $stateMachine->getPossibleTransitions()->shouldBeCalled()->willReturn(['transition', 'transition2']); - $this->getEnabledTransition($subject, 'some_graph')->shouldBeLike([ + $this->getEnabledTransitions($subject, 'some_graph')->shouldBeLike([ new Transition('transition', null, null), new Transition('transition2', null, null), ]); @@ -93,6 +93,6 @@ function it_translates_winzou_state_machines_exceptions_to_state_machine_executi $winzouStateMachineFactory->get($subject, 'some_graph')->willReturn($stateMachine); $stateMachine->getPossibleTransitions()->willThrow(new SMException('Invalid argument')); - $this->shouldThrow(StateMachineExecutionException::class)->during('getEnabledTransition', [$subject, 'some_graph']); + $this->shouldThrow(StateMachineExecutionException::class)->during('getEnabledTransitions', [$subject, 'some_graph']); } } From ff234f2b0fb318fe8bdae3e512048372027b565f Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 16:36:21 +0200 Subject: [PATCH 18/25] Add Twig Functions for the new State Machine abstraction --- .../CoreBundle/Twig/StateMachineExtension.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/Sylius/Bundle/CoreBundle/Twig/StateMachineExtension.php diff --git a/src/Sylius/Bundle/CoreBundle/Twig/StateMachineExtension.php b/src/Sylius/Bundle/CoreBundle/Twig/StateMachineExtension.php new file mode 100644 index 00000000000..8a6ac8cd6a5 --- /dev/null +++ b/src/Sylius/Bundle/CoreBundle/Twig/StateMachineExtension.php @@ -0,0 +1,25 @@ + + */ + public function getFunctions(): array + { + return [ + new TwigFunction('sylius_state_machine_can', [$this->stateMachine, 'can']), + new TwigFunction('sylius_state_machine_get_enabled_transitions', [$this->stateMachine, 'getEnabledTransitions']), + ]; + } +} From 00c3422ac94cbd7f06b02ac61011ba74558fb4d5 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 16:36:59 +0200 Subject: [PATCH 19/25] Apply ECS fixes --- .../Exception/StateMachineExecutionException.php | 11 +++++++++++ .../CoreBundle/StateMachine/StateMachineInterface.php | 1 + .../Bundle/CoreBundle/Twig/StateMachineExtension.php | 11 +++++++++++ 3 files changed, 23 insertions(+) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/Exception/StateMachineExecutionException.php b/src/Sylius/Bundle/CoreBundle/StateMachine/Exception/StateMachineExecutionException.php index fbb220ac4f0..d30bbf21d39 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/Exception/StateMachineExecutionException.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/Exception/StateMachineExecutionException.php @@ -1,5 +1,16 @@ */ public function getEnabledTransitions(object $subject, string $graphName): array; diff --git a/src/Sylius/Bundle/CoreBundle/Twig/StateMachineExtension.php b/src/Sylius/Bundle/CoreBundle/Twig/StateMachineExtension.php index 8a6ac8cd6a5..429cd2ab672 100644 --- a/src/Sylius/Bundle/CoreBundle/Twig/StateMachineExtension.php +++ b/src/Sylius/Bundle/CoreBundle/Twig/StateMachineExtension.php @@ -1,5 +1,16 @@ Date: Sat, 16 Sep 2023 16:40:01 +0200 Subject: [PATCH 20/25] Add a missing array typing --- .../Bundle/CoreBundle/StateMachine/CompositeStateMachine.php | 3 +++ .../Bundle/CoreBundle/StateMachine/StateMachineInterface.php | 2 ++ src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php index 1fc977462cb..e6b304dd458 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -22,6 +22,9 @@ class CompositeStateMachine implements StateMachineInterface /** @var array */ private array $stateMachineAdapters; + /** + * @param iterable $stateMachineAdapters + */ public function __construct(iterable $stateMachineAdapters) { Assert::notEmpty($stateMachineAdapters, 'At least one state machine adapter should be provided.'); diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php b/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php index db11c187e16..e352949c7e5 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/StateMachineInterface.php @@ -23,6 +23,8 @@ interface StateMachineInterface public function can(object $subject, string $graphName, string $transition): bool; /** + * @param array $context + * * @throws StateMachineExecutionException */ public function apply(object $subject, string $graphName, string $transition, array $context = []): void; diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php b/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php index 9bd01216c0e..d0710bc1d0e 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php @@ -15,6 +15,11 @@ final class Transition implements TransitionInterface { + /** + * @param string $name + * @param array|null $froms + * @param array|null $tos + */ public function __construct( private string $name, private ?array $froms, From 354dda02c88810114747f3ce096ebefb52c7fb95 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 18:05:54 +0200 Subject: [PATCH 21/25] Silence the error telling Symfony\Component\Workflow\Registry::get is an internal method --- psalm.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/psalm.xml b/psalm.xml index ecbe40dc73f..daca11c0e18 100644 --- a/psalm.xml +++ b/psalm.xml @@ -98,6 +98,7 @@ + From cf2a7191d959d8426438d4c017651950826a5915 Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 22:05:38 +0200 Subject: [PATCH 22/25] Change expected exception to the correct one --- .../Functional/StateMachine/StateMachineCompositeTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php index 20c3ca09338..2c9ff3829c2 100644 --- a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php +++ b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php @@ -13,9 +13,9 @@ namespace Sylius\Bundle\CoreBundle\Tests\Functional\StateMachine; -use SM\SMException; use Sylius\Bundle\CoreBundle\Application\Model\BlogPost; use Sylius\Bundle\CoreBundle\Application\Model\Comment; +use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; use Sylius\Bundle\CoreBundle\StateMachine\StateMachineInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -49,7 +49,7 @@ public function it_throws_the_last_exception_thrown_by_the_state_machine_adapter $comment = new Comment(); - $this->expectException(SMException::class); + $this->expectException(StateMachineExecutionException::class); $this->expectExceptionMessage('Cannot create a state machine because the configuration for object "Sylius\Bundle\CoreBundle\Application\Model\Comment" with graph "app_blog_comment" does not exist'); $stateMachine->apply($comment, 'app_blog_comment', 'publish'); From 8a3df8aacb4e8be71264597f850c86cdab1ca3dd Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sat, 16 Sep 2023 22:06:46 +0200 Subject: [PATCH 23/25] Enable Symfony Workflow in AdminBundle and ApiBundle --- src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml | 1 + src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml b/src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml index dca50202dc8..dcdbff194e8 100644 --- a/src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml +++ b/src/Sylius/Bundle/AdminBundle/test/config/packages/config.yaml @@ -15,6 +15,7 @@ framework: test: ~ mailer: dsn: 'null://null' + workflows: ~ security: firewalls: diff --git a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml index dbf7252c6ee..016b52f50f3 100644 --- a/src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml +++ b/src/Sylius/Bundle/ApiBundle/Tests/Application/config/config.yaml @@ -39,6 +39,7 @@ framework: paths: ['%kernel.project_dir%/config/serialization'] mailer: dsn: 'null://null' + workflows: ~ doctrine_migrations: storage: From 83c09f83d8a340f6422bac5761e3aca18ddf7f3b Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sun, 17 Sep 2023 18:08:42 +0200 Subject: [PATCH 24/25] Allow configuring a default state machine adapter and a per graph adapter --- .../DependencyInjection/Configuration.php | 14 +++++ .../SyliusCoreExtension.php | 2 + .../DependencyInjection/ConfigurationTest.php | 60 +++++++++++++++++++ 3 files changed, 76 insertions(+) diff --git a/src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php index 06dc91b8346..dbd4e360b05 100644 --- a/src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php +++ b/src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php @@ -98,6 +98,20 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() + ->arrayNode('state_machine') + ->addDefaultsIfNotSet() + ->children() + ->scalarNode('default_adapter')->defaultValue('winzou_state_machine')->end() + ->arrayNode('adapters_mapping') + ->arrayPrototype() + ->children() + ->scalarNode('graph_name')->cannotBeEmpty()->end() + ->scalarNode('adapter')->cannotBeEmpty()->end() + ->end() + ->end() + ->end() + ->end() + ->end() ->end() ; diff --git a/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php b/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php index a38be15f801..18a4cc60236 100644 --- a/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php +++ b/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php @@ -72,6 +72,8 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('sylius_core.order_by_identifier', $config['order_by_identifier']); $container->setParameter('sylius_core.catalog_promotions.batch_size', $config['catalog_promotions']['batch_size']); $container->setParameter('sylius_core.price_history.batch_size', $config['price_history']['batch_size']); + $container->setParameter('sylius_core.state_machine.default_adapter', $config['state_machine']['default_adapter']); + $container->setParameter('sylius_core.state_machine.adapters_mapping', $config['state_machine']['adapters_mapping']); /** @var string $env */ $env = $container->getParameter('kernel.environment'); diff --git a/src/Sylius/Bundle/CoreBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Sylius/Bundle/CoreBundle/Tests/DependencyInjection/ConfigurationTest.php index 2a42d1a323d..a1913eb1bfd 100644 --- a/src/Sylius/Bundle/CoreBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Sylius/Bundle/CoreBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -72,6 +72,66 @@ public function it_allows_to_disable_order_by_identifier(): void ); } + /** @test */ + public function it_allows_to_configure_a_default_state_machine_adapter(): void + { + $this->assertProcessedConfigurationEquals( + [ + [ + 'state_machine' => [ + 'default_adapter' => 'symfony_workflow', + ], + ], + ], + [ + 'state_machine' => [ + 'default_adapter' => 'symfony_workflow', + 'adapters_mapping' => [], + ], + ], + 'state_machine', + ); + } + + /** @test */ + public function it_allows_to_configure_the_state_machines_adapters_mapping(): void + { + $this->assertProcessedConfigurationEquals( + [ + [ + 'state_machine' => [ + 'adapters_mapping' => [ + [ + 'graph_name' => 'order', + 'adapter' => 'symfony_workflow', + ], + [ + 'graph_name' => 'payment', + 'adapter' => 'winzou_state_machine', + ], + ], + ], + ], + ], + [ + 'state_machine' => [ + 'default_adapter' => 'winzou_state_machine', + 'adapters_mapping' => [ + [ + 'graph_name' => 'order', + 'adapter' => 'symfony_workflow', + ], + [ + 'graph_name' => 'payment', + 'adapter' => 'winzou_state_machine', + ], + ], + ], + ], + 'state_machine', + ); + } + /** @test */ public function it_throws_an_exception_if_value_other_then_integer_is_declared_as_batch_size(): void { From 258ee8b5549cb53408ea0a4680597e01877d95ec Mon Sep 17 00:00:00 2001 From: Jacob Tobiasz Date: Sun, 17 Sep 2023 20:08:30 +0200 Subject: [PATCH 25/25] Make use of configurable state machine adapters in the composite state machine --- .../DependencyInjection/Configuration.php | 10 +- .../SyliusCoreExtension.php | 2 +- .../config/services/state_machines.xml | 8 +- .../StateMachine/CompositeStateMachine.php | 50 ++--- .../CoreBundle/StateMachine/Transition.php | 1 - .../DependencyInjection/ConfigurationTest.php | 26 +-- .../StateMachineCompositeTest.php | 27 +-- .../CompositeStateMachineSpec.php | 197 ++++++++++-------- .../test/config/packages/state_machine.yaml | 10 +- 9 files changed, 153 insertions(+), 178 deletions(-) diff --git a/src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php b/src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php index dbd4e360b05..7fe6d7db1c8 100644 --- a/src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php +++ b/src/Sylius/Bundle/CoreBundle/DependencyInjection/Configuration.php @@ -102,13 +102,9 @@ public function getConfigTreeBuilder(): TreeBuilder ->addDefaultsIfNotSet() ->children() ->scalarNode('default_adapter')->defaultValue('winzou_state_machine')->end() - ->arrayNode('adapters_mapping') - ->arrayPrototype() - ->children() - ->scalarNode('graph_name')->cannotBeEmpty()->end() - ->scalarNode('adapter')->cannotBeEmpty()->end() - ->end() - ->end() + ->arrayNode('graphs_to_adapters_mapping') + ->useAttributeAsKey('graph_name') + ->scalarPrototype()->end() ->end() ->end() ->end() diff --git a/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php b/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php index 18a4cc60236..3f01740907d 100644 --- a/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php +++ b/src/Sylius/Bundle/CoreBundle/DependencyInjection/SyliusCoreExtension.php @@ -73,7 +73,7 @@ public function load(array $configs, ContainerBuilder $container): void $container->setParameter('sylius_core.catalog_promotions.batch_size', $config['catalog_promotions']['batch_size']); $container->setParameter('sylius_core.price_history.batch_size', $config['price_history']['batch_size']); $container->setParameter('sylius_core.state_machine.default_adapter', $config['state_machine']['default_adapter']); - $container->setParameter('sylius_core.state_machine.adapters_mapping', $config['state_machine']['adapters_mapping']); + $container->setParameter('sylius_core.state_machine.graphs_to_adapters_mapping', $config['state_machine']['graphs_to_adapters_mapping']); /** @var string $env */ $env = $container->getParameter('kernel.environment'); diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml index 91f85dbcda6..4a48356fd53 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_machines.xml @@ -17,7 +17,9 @@ - + + %sylius_core.state_machine.default_adapter% + %sylius_core.state_machine.graphs_to_adapters_mapping% @@ -27,14 +29,14 @@ - + - + diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php index e6b304dd458..2771e8dd7e2 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/CompositeStateMachine.php @@ -13,7 +13,6 @@ namespace Sylius\Bundle\CoreBundle\StateMachine; -use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; use Traversable; use Webmozart\Assert\Assert; @@ -24,9 +23,13 @@ class CompositeStateMachine implements StateMachineInterface /** * @param iterable $stateMachineAdapters + * @param array $graphsToAdaptersMapping */ - public function __construct(iterable $stateMachineAdapters) - { + public function __construct( + iterable $stateMachineAdapters, + private string $defaultAdapter, + private array $graphsToAdaptersMapping, + ) { Assert::notEmpty($stateMachineAdapters, 'At least one state machine adapter should be provided.'); Assert::allIsInstanceOf( $stateMachineAdapters, @@ -41,17 +44,7 @@ public function __construct(iterable $stateMachineAdapters) */ public function can(object $subject, string $graphName, string $transition): bool { - $lastException = new \Exception(); - - foreach ($this->stateMachineAdapters as $stateMachineAdapter) { - try { - return $stateMachineAdapter->can($subject, $graphName, $transition); - } catch (StateMachineExecutionException $exception) { - $lastException = $exception; - } - } - - throw $lastException; + return $this->getStateMachineAdapter($graphName)->can($subject, $graphName, $transition); } /** @@ -59,19 +52,7 @@ public function can(object $subject, string $graphName, string $transition): boo */ public function apply(object $subject, string $graphName, string $transition, array $context = []): void { - $lastException = new \Exception(); - - foreach ($this->stateMachineAdapters as $stateMachineAdapter) { - try { - $stateMachineAdapter->apply($subject, $graphName, $transition, $context); - - return; - } catch (StateMachineExecutionException $exception) { - $lastException = $exception; - } - } - - throw $lastException; + $this->getStateMachineAdapter($graphName)->apply($subject, $graphName, $transition, $context); } /** @@ -79,16 +60,15 @@ public function apply(object $subject, string $graphName, string $transition, ar */ public function getEnabledTransitions(object $subject, string $graphName): array { - $lastException = new \Exception(); + return $this->getStateMachineAdapter($graphName)->getEnabledTransitions($subject, $graphName); + } - foreach ($this->stateMachineAdapters as $stateMachineAdapter) { - try { - return $stateMachineAdapter->getEnabledTransitions($subject, $graphName); - } catch (StateMachineExecutionException $exception) { - $lastException = $exception; - } + private function getStateMachineAdapter(string $graphName): StateMachineInterface + { + if (isset($this->graphsToAdaptersMapping[$graphName])) { + return $this->stateMachineAdapters[$this->graphsToAdaptersMapping[$graphName]]; } - throw $lastException; + return $this->stateMachineAdapters[$this->defaultAdapter]; } } diff --git a/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php b/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php index d0710bc1d0e..5f007c3005f 100644 --- a/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php +++ b/src/Sylius/Bundle/CoreBundle/StateMachine/Transition.php @@ -16,7 +16,6 @@ final class Transition implements TransitionInterface { /** - * @param string $name * @param array|null $froms * @param array|null $tos */ diff --git a/src/Sylius/Bundle/CoreBundle/Tests/DependencyInjection/ConfigurationTest.php b/src/Sylius/Bundle/CoreBundle/Tests/DependencyInjection/ConfigurationTest.php index a1913eb1bfd..84fc06b2d9a 100644 --- a/src/Sylius/Bundle/CoreBundle/Tests/DependencyInjection/ConfigurationTest.php +++ b/src/Sylius/Bundle/CoreBundle/Tests/DependencyInjection/ConfigurationTest.php @@ -86,7 +86,7 @@ public function it_allows_to_configure_a_default_state_machine_adapter(): void [ 'state_machine' => [ 'default_adapter' => 'symfony_workflow', - 'adapters_mapping' => [], + 'graphs_to_adapters_mapping' => [], ], ], 'state_machine', @@ -100,15 +100,9 @@ public function it_allows_to_configure_the_state_machines_adapters_mapping(): vo [ [ 'state_machine' => [ - 'adapters_mapping' => [ - [ - 'graph_name' => 'order', - 'adapter' => 'symfony_workflow', - ], - [ - 'graph_name' => 'payment', - 'adapter' => 'winzou_state_machine', - ], + 'graphs_to_adapters_mapping' => [ + 'order' => 'symfony_workflow', + 'payment' => 'winzou_state_machine', ], ], ], @@ -116,15 +110,9 @@ public function it_allows_to_configure_the_state_machines_adapters_mapping(): vo [ 'state_machine' => [ 'default_adapter' => 'winzou_state_machine', - 'adapters_mapping' => [ - [ - 'graph_name' => 'order', - 'adapter' => 'symfony_workflow', - ], - [ - 'graph_name' => 'payment', - 'adapter' => 'winzou_state_machine', - ], + 'graphs_to_adapters_mapping' => [ + 'order' => 'symfony_workflow', + 'payment' => 'winzou_state_machine', ], ], ], diff --git a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php index 2c9ff3829c2..b2888e855f0 100644 --- a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php +++ b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/StateMachineCompositeTest.php @@ -15,44 +15,29 @@ use Sylius\Bundle\CoreBundle\Application\Model\BlogPost; use Sylius\Bundle\CoreBundle\Application\Model\Comment; -use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; use Sylius\Bundle\CoreBundle\StateMachine\StateMachineInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; final class StateMachineCompositeTest extends KernelTestCase { /** @test */ - public function it_returns_whether_a_transition_can_be_applied_basing_on_the_state_machine_adapter_with_the_highest_priority(): void + public function it_calls_a_method_on_a_default_state_machine_adapter_when_mapping_is_not_configured_for_a_given_graph(): void { $stateMachine = $this->getStateMachine(); - $blogPost = new BlogPost(); + $subject = new BlogPost(); - $this->assertTrue($stateMachine->can($blogPost, 'app_blog_post', 'publish')); - $this->assertFalse($stateMachine->can($blogPost, 'app_blog_post', 'post')); + $this->assertTrue($stateMachine->can($subject, 'app_blog_post', 'publish')); } /** @test */ - public function it_returns_whether_a_transition_can_be_applied_fallback_to_the_state_machine_adapters_with_the_lower_priority(): void + public function it_calls_a_method_on_a_configured_adapter_for_a_given_graph(): void { $stateMachine = $this->getStateMachine(); - $comment = new Comment(); + $subject = new Comment(); - $this->assertTrue($stateMachine->can($comment, 'app_comment', 'publish')); - } - - /** @test */ - public function it_throws_the_last_exception_thrown_by_the_state_machine_adapters(): void - { - $stateMachine = $this->getStateMachine(); - - $comment = new Comment(); - - $this->expectException(StateMachineExecutionException::class); - $this->expectExceptionMessage('Cannot create a state machine because the configuration for object "Sylius\Bundle\CoreBundle\Application\Model\Comment" with graph "app_blog_comment" does not exist'); - - $stateMachine->apply($comment, 'app_blog_comment', 'publish'); + $this->assertTrue($stateMachine->can($subject, 'app_comment', 'post')); } private function getStateMachine(): StateMachineInterface diff --git a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php index 7593ff01a66..defb811c469 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/StateMachine/CompositeStateMachineSpec.php @@ -14,120 +14,139 @@ namespace spec\Sylius\Bundle\CoreBundle\StateMachine; use PhpSpec\ObjectBehavior; -use Sylius\Bundle\CoreBundle\StateMachine\Exception\StateMachineExecutionException; use Sylius\Bundle\CoreBundle\StateMachine\StateMachineInterface; -use Webmozart\Assert\InvalidArgumentException; final class CompositeStateMachineSpec extends ObjectBehavior { - function it_throws_an_exception_when_no_state_machine_is_passed(): void - { - $this->beConstructedWith([]); - $this - ->shouldThrow( - new InvalidArgumentException('At least one state machine adapter should be provided.'), - )->duringInstantiation() - ; - } + function it_invokes_the_can_method_on_a_default_state_machine_adapter_when_no_state_machine_is_mapped_to_a_given_graph( + StateMachineInterface $winzouStateMachineAdapter, + StateMachineInterface $symfonyWorkflowAdapter, + ): void { + $this->beConstructedWith( + [ + 'winzou_state_machine' => $winzouStateMachineAdapter, + 'symfony_workflow' => $symfonyWorkflowAdapter, + ], + 'winzou_state_machine', + [], + ); - function it_throws_an_exception_when_any_of_passed_objects_is_not_a_state_machine(): void - { - $this->beConstructedWith([new \stdClass()]); - $this - ->shouldThrow( - new InvalidArgumentException( - sprintf('All state machine adapters should implement the "%s" interface.', StateMachineInterface::class), - ), - )->duringInstantiation() - ; - } + $subject = new \stdClass(); - function it_does_not_throw_an_exception_when_all_passed_objects_are_state_machines( - StateMachineInterface $stateMachineOne, - StateMachineInterface $stateMachineTwo, - ): void { - $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->shouldNotThrow(InvalidArgumentException::class)->duringInstantiation(); + $winzouStateMachineAdapter->can($subject, 'my_graph', 'my_transition')->willReturn(true); + $symfonyWorkflowAdapter->can($subject, 'my_graph', 'my_transition')->shouldNotBeCalled(); + + $this->can($subject, 'my_graph', 'my_transition')->shouldReturn(true); } - function it_invokes_the_can_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( - StateMachineInterface $stateMachineOne, - StateMachineInterface $stateMachineTwo, - \stdClass $subject, + function it_invokes_the_can_method_on_a_state_machine_assigned_to_a_given_graph( + StateMachineInterface $winzouStateMachineAdapter, + StateMachineInterface $symfonyWorkflowAdapter, ): void { - $stateMachineOne->can($subject, 'graph', 'transition')->willThrow(new StateMachineExecutionException()); - $stateMachineTwo->can($subject, 'graph', 'transition')->willReturn(false); - - $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->can($subject, 'graph', 'transition')->shouldReturn(false); + $this->beConstructedWith( + [ + 'winzou_state_machine' => $winzouStateMachineAdapter, + 'symfony_workflow' => $symfonyWorkflowAdapter, + ], + 'winzou_state_machine', + [ + 'my_graph' => 'symfony_workflow', + ], + ); + + $subject = new \stdClass(); + + $winzouStateMachineAdapter->can($subject, 'my_graph', 'my_transition')->shouldNotBeCalled(); + $symfonyWorkflowAdapter->can($subject, 'my_graph', 'my_transition')->willReturn(true); + + $this->can($subject, 'my_graph', 'my_transition')->shouldReturn(true); } - function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_can_call( - StateMachineInterface $stateMachineOne, - StateMachineInterface $stateMachineTwo, - \stdClass $subject, + function it_invokes_the_apply_method_on_a_default_state_machine_adapter_when_no_state_machine_is_mapped_to_a_given_graph( + StateMachineInterface $winzouStateMachineAdapter, + StateMachineInterface $symfonyWorkflowAdapter, ): void { - $firstException = new StateMachineExecutionException(); - $secondException = new StateMachineExecutionException(); + $this->beConstructedWith( + [ + 'winzou_state_machine' => $winzouStateMachineAdapter, + 'symfony_workflow' => $symfonyWorkflowAdapter, + ], + 'winzou_state_machine', + [], + ); + + $subject = new \stdClass(); - $stateMachineOne->can($subject, 'graph', 'transition')->willThrow($firstException); - $stateMachineTwo->can($subject, 'graph', 'transition')->willThrow($secondException); + $winzouStateMachineAdapter->apply($subject, 'my_graph', 'my_transition', [])->shouldBeCalled(); + $symfonyWorkflowAdapter->apply($subject, 'my_graph', 'my_transition', [])->shouldNotBeCalled(); - $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->shouldThrow($secondException)->during('can', [$subject, 'graph', 'transition']); + $this->apply($subject, 'my_graph', 'my_transition'); } - function it_invokes_the_apply_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( - StateMachineInterface $stateMachineOne, - StateMachineInterface $stateMachineTwo, - \stdClass $subject, + function it_invokes_the_apply_method_on_a_state_machine_assigned_to_a_given_graph( + StateMachineInterface $winzouStateMachineAdapter, + StateMachineInterface $symfonyWorkflowAdapter, ): void { - $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow(new StateMachineExecutionException()); - $stateMachineTwo->apply($subject, 'graph', 'transition', [])->shouldBeCalled(); - - $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->apply($subject, 'graph', 'transition'); + $this->beConstructedWith( + [ + 'winzou_state_machine' => $winzouStateMachineAdapter, + 'symfony_workflow' => $symfonyWorkflowAdapter, + ], + 'winzou_state_machine', + [ + 'my_graph' => 'symfony_workflow', + ], + ); + + $subject = new \stdClass(); + + $winzouStateMachineAdapter->apply($subject, 'my_graph', 'my_transition', [])->shouldNotBeCalled(); + $symfonyWorkflowAdapter->apply($subject, 'my_graph', 'my_transition', [])->shouldBeCalled(); + + $this->apply($subject, 'my_graph', 'my_transition'); } - function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_an_apply_call( - StateMachineInterface $stateMachineOne, - StateMachineInterface $stateMachineTwo, - \stdClass $subject, + function it_invokes_the_get_enabled_transitions_method_on_a_default_state_machine_adapter_when_no_state_machine_is_mapped_to_a_given_graph( + StateMachineInterface $winzouStateMachineAdapter, + StateMachineInterface $symfonyWorkflowAdapter, ): void { - $firstException = new StateMachineExecutionException(); - $secondException = new StateMachineExecutionException(); - - $stateMachineOne->apply($subject, 'graph', 'transition', [])->willThrow($firstException); - $stateMachineTwo->apply($subject, 'graph', 'transition', [])->willThrow($secondException); + $this->beConstructedWith( + [ + 'winzou_state_machine' => $winzouStateMachineAdapter, + 'symfony_workflow' => $symfonyWorkflowAdapter, + ], + 'winzou_state_machine', + [], + ); - $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->shouldThrow($secondException)->during('apply', [$subject, 'graph', 'transition']); - } + $subject = new \stdClass(); - function it_invokes_the_get_enabled_transition_method_on_a_first_state_machine_with_a_configured_graph_for_a_given_subject( - StateMachineInterface $stateMachineOne, - StateMachineInterface $stateMachineTwo, - \stdClass $subject, - ): void { - $stateMachineOne->getEnabledTransitions($subject, 'graph')->willThrow(new StateMachineExecutionException()); - $stateMachineTwo->getEnabledTransitions($subject, 'graph')->willReturn([]); + $winzouStateMachineAdapter->getEnabledTransitions($subject, 'my_graph')->shouldBeCalled()->willReturn([]); + $symfonyWorkflowAdapter->getEnabledTransitions($subject, 'my_graph')->shouldNotBeCalled(); - $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->getEnabledTransitions($subject, 'graph')->shouldReturn([]); + $this->getEnabledTransitions($subject, 'my_graph')->shouldReturn([]); } - function it_throws_the_last_caught_exception_when_no_state_machine_is_configured_for_a_given_subject_on_a_get_enabled_transition_call( - StateMachineInterface $stateMachineOne, - StateMachineInterface $stateMachineTwo, - \stdClass $subject, + function it_invokes_the_get_enabled_transitions_method_on_a_state_machine_assigned_to_a_given_graph( + StateMachineInterface $winzouStateMachineAdapter, + StateMachineInterface $symfonyWorkflowAdapter, ): void { - $firstException = new StateMachineExecutionException(); - $secondException = new StateMachineExecutionException(); - - $stateMachineOne->getEnabledTransitions($subject, 'graph')->willThrow($firstException); - $stateMachineTwo->getEnabledTransitions($subject, 'graph')->willThrow($secondException); - - $this->beConstructedWith([$stateMachineOne, $stateMachineTwo]); - $this->shouldThrow($secondException)->during('getEnabledTransitions', [$subject, 'graph']); + $this->beConstructedWith( + [ + 'winzou_state_machine' => $winzouStateMachineAdapter, + 'symfony_workflow' => $symfonyWorkflowAdapter, + ], + 'winzou_state_machine', + [ + 'my_graph' => 'symfony_workflow', + ], + ); + + $subject = new \stdClass(); + + $winzouStateMachineAdapter->getEnabledTransitions($subject, 'my_graph')->shouldNotBeCalled(); + $symfonyWorkflowAdapter->getEnabledTransitions($subject, 'my_graph')->shouldBeCalled()->willReturn([]); + + $this->getEnabledTransitions($subject, 'my_graph')->shouldReturn([]); } } diff --git a/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml b/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml index e76ed612c6c..9af8b746cbb 100644 --- a/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml +++ b/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml @@ -25,10 +25,10 @@ winzou_state_machine: published: ~ unpublished: ~ transitions: - publish: + post: from: [new, unpublished] to: published - unpublish: + unpost: from: [published] to: unpublished @@ -53,3 +53,9 @@ framework: unpublish: from: published to: unpublished + +sylius_core: + state_machine: + default_adapter: 'winzou_state_machine' + graphs_to_adapters_mapping: + app_blog_post: 'symfony_workflow'