diff --git a/.github/workflows/ci_e2e-mariadb.yaml b/.github/workflows/ci_e2e-mariadb.yaml index 5aea23e66c5..c1470658957 100644 --- a/.github/workflows/ci_e2e-mariadb.yaml +++ b/.github/workflows/ci_e2e-mariadb.yaml @@ -44,7 +44,7 @@ jobs: behat-no-js: needs: get-matrix runs-on: ubuntu-latest - name: "Non-JS, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MariaDB ${{ matrix.mariadb }}" + name: "Non-JS, PHP ${{ matrix.php }}, Symfony ${{ matrix.symfony }}, MariaDB ${{ matrix.mariadb }}, State Machine Adapter ${{ matrix.state_machine_adapter }}" timeout-minutes: 45 strategy: fail-fast: false @@ -53,6 +53,7 @@ jobs: env: APP_ENV: test_cached DATABASE_URL: "mysql://root:root@127.0.0.1/sylius?charset=utf8mb4&serverVersion=mariadb-${{ matrix.mariadb }}" + TEST_SYLIUS_STATE_MACHINE_ADAPTER: "${{ matrix.state_machine_adapter }}" steps: - name: Set variables diff --git a/.github/workflows/matrix.json b/.github/workflows/matrix.json index 9168c13afbf..d4e10eb3a1e 100644 --- a/.github/workflows/matrix.json +++ b/.github/workflows/matrix.json @@ -19,13 +19,15 @@ { "php": "8.0", "symfony": "^5.4", - "mariadb": "10.4.10" + "mariadb": "10.4.10", + "state_machine_adapter": "winzou_state_machine" }, { "php": "8.2", "symfony": "^6.4", "mariadb": "10.4.10", - "dbal": "^3.0" + "dbal": "^3.0", + "state_machine_adapter": "symfony_workflow" } ] }, @@ -98,6 +100,7 @@ "php": [ "8.0", "8.1", "8.2" ], "symfony": [ "^5.4", "^6.4" ], "mariadb": [ "10.4.10" ], + "state_machine_adapter": [ "winzou_state_machine", "symfony_workflow" ], "exclude": [ { "php": "8.0", diff --git a/UPGRADE-1.13.md b/UPGRADE-1.13.md index 968a3fec890..5faabe9d14b 100644 --- a/UPGRADE-1.13.md +++ b/UPGRADE-1.13.md @@ -1,5 +1,27 @@ # UPGRADE FROM `v1.12.X` TO `v1.13.0` +1. Starting with Sylius `1.13` we provided a possibility to use the Symfony Workflow as your State Machine. To allow a smooth transition + we created a new package called `sylius/state-machine-abstraction`, + which provides a configurable abstraction, + allowing you to define which adapter should be used (Winzou State Machine or Symfony Workflow) per graph. + Starting with Sylius `1.13` we configure all existing Sylius' graphs to use the Symfony Workflow. + At the same time, all other graphs are using Winzou State Machine as a default state machine, + and must be configured manually to use the Symfony Workflow. + + Configuration: + ```yaml + sylius_state_machine_abstraction: + graphs_to_adapters_mapping: + : + # e.g. + sylius_order_checkout: symfony_workflow # available adapters: symfony_workflow, winzou_state_machine + + # we can also can the default adapter + default_adapter: symfony_workflow # winzou_state_machine is a default value here + ``` + + > **Note!** Starting with Sylius `2.0` the Symfony Workflow will be the default state machine for all graphs. + 1. A new parameter has been added to specify the validation groups for given gateway factory. If you have any custom validation groups for your factory, you need to add them to your `config/packages/_sylius.yaml` file. Also, if you have your own gateway factory and want to add your validation groups you can add another entry to the `validation_groups` configuration node. @@ -19,7 +41,7 @@ - 'your_custom_validation_group' ``` -1. New parameters have been added to specify the validation groups for given shipping method rules and calculators. +1. There have been a naw parameters added to specify the validation groups for given shipping method rule and calculator. If you have any custom validation groups for your calculator or rules, you need to add them to your `config/packages/_sylius.yaml` file. Also, if you have your own shipping method rule or calculator and want to add your validation groups you can add another key to the `validation_groups` parameter. @@ -58,7 +80,7 @@ 1. Class `Sylius\Bundle\ProductBundle\Form\Type\ProductOptionChoiceType` has been deprecated. Use `Sylius\Bundle\ProductBundle\Form\Type\ProductOptionAutocompleteType` instead. -1. Using `parentId` query parameter to generate the slug in `Sylius\Bundle\TaxonomyBundle\Controller\TaxonSlugController` has been deprecated. +1. Using `parentId` query parameter to generate slug in `Sylius\Bundle\TaxonomyBundle\Controller\TaxonSlugController` has been deprecated. Use the `parentCode` query parameter instead. 1. Starting with Sylius 1.13, the `SyliusPriceHistoryPlugin` is included. @@ -75,10 +97,6 @@ 1. The name of the second argument of `Sylius\Bundle\AdminBundle\Action\ResendOrderConfirmationEmailAction` is deprecated and will be renamed to `$resendOrderConfirmationEmailDispatcher`. -1. Passing `Sylius\Bundle\AdminBundle\EmailManager\ShipmentEmailManagerInterface` to `Sylius\Bundle\AdminBundle\Action\ResendShipmentConfirmationEmailAction` as a second argument is deprecated, use `Sylius\Bundle\CoreBundle\MessageDispatcher\ResendShipmentConfirmationEmailDispatcherInterface` instead. - -1. The name of the second argument of `Sylius\Bundle\AdminBundle\Action\ResendShipmentConfirmationEmailAction` is deprecated and will be renamed to `$resendShipmentConfirmationEmailDispatcher`. - 1. Not passing `Sylius\Bundle\CoreBundle\CatalogPromotion\Announcer\CatalogPromotionRemovalAnnouncerInterface` to `Sylius\Bundle\CoreBundle\CatalogPromotion\Processor\CatalogPromotionRemovalProcessor` as a second argument is deprecated. diff --git a/config/packages/test/_sylius.yaml b/config/packages/test/_sylius.yaml index eea643da6aa..247617febac 100644 --- a/config/packages/test/_sylius.yaml +++ b/config/packages/test/_sylius.yaml @@ -1,5 +1,20 @@ +parameters: + test_default_state_machine_adapter: 'symfony_workflow' + test_sylius_state_machine_adapter: '%env(string:default:test_default_state_machine_adapter:TEST_SYLIUS_STATE_MACHINE_ADAPTER)%' + sylius_user: encoder: plaintext sylius_api: enabled: true + +sylius_state_machine_abstraction: + graphs_to_adapters_mapping: + sylius_catalog_promotion: '%test_sylius_state_machine_adapter%' + sylius_order: '%test_sylius_state_machine_adapter%' + sylius_order_checkout: '%test_sylius_state_machine_adapter%' + sylius_order_payment: '%test_sylius_state_machine_adapter%' + sylius_order_shipping: '%test_sylius_state_machine_adapter%' + sylius_payment: '%test_sylius_state_machine_adapter%' + sylius_product_review: '%test_sylius_state_machine_adapter%' + sylius_shipment: '%test_sylius_state_machine_adapter%' diff --git a/src/Sylius/Abstraction/StateMachine/config/services.xml b/src/Sylius/Abstraction/StateMachine/config/services.xml index b3c0d505a3c..8ec1b9840e4 100644 --- a/src/Sylius/Abstraction/StateMachine/config/services.xml +++ b/src/Sylius/Abstraction/StateMachine/config/services.xml @@ -26,14 +26,12 @@ - - diff --git a/src/Sylius/Abstraction/StateMachine/src/CompositeStateMachine.php b/src/Sylius/Abstraction/StateMachine/src/CompositeStateMachine.php index 92c15e70458..7f383d25032 100644 --- a/src/Sylius/Abstraction/StateMachine/src/CompositeStateMachine.php +++ b/src/Sylius/Abstraction/StateMachine/src/CompositeStateMachine.php @@ -63,6 +63,16 @@ public function getEnabledTransitions(object $subject, string $graphName): array return $this->getStateMachineAdapter($graphName)->getEnabledTransitions($subject, $graphName); } + public function getTransitionFromState(object $subject, string $graphName, string $fromState): ?string + { + return $this->getStateMachineAdapter($graphName)->getTransitionFromState($subject, $graphName, $fromState); + } + + public function getTransitionToState(object $subject, string $graphName, string $toState): ?string + { + return $this->getStateMachineAdapter($graphName)->getTransitionToState($subject, $graphName, $toState); + } + private function getStateMachineAdapter(string $graphName): StateMachineInterface { if (isset($this->graphsToAdaptersMapping[$graphName])) { diff --git a/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/Configuration.php b/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/Configuration.php index e83445d1775..57d7088fbd8 100644 --- a/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/Configuration.php +++ b/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/Configuration.php @@ -21,22 +21,17 @@ final class Configuration implements ConfigurationInterface { public function getConfigTreeBuilder(): TreeBuilder { - $treeBuilder = new TreeBuilder('sylius_abstraction'); + $treeBuilder = new TreeBuilder('sylius_state_machine_abstraction'); /** @var ArrayNodeDefinition $rootNode */ $rootNode = $treeBuilder->getRootNode(); $rootNode ->addDefaultsIfNotSet() ->children() - ->arrayNode('state_machine') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('default_adapter')->defaultValue('winzou_state_machine')->end() - ->arrayNode('graphs_to_adapters_mapping') - ->useAttributeAsKey('graph_name') - ->scalarPrototype()->end() - ->end() - ->end() + ->scalarNode('default_adapter')->defaultValue('winzou_state_machine')->end() + ->arrayNode('graphs_to_adapters_mapping') + ->useAttributeAsKey('graph_name') + ->scalarPrototype()->end() ->end() ->end() ; diff --git a/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/SyliusStateMachineAbstractionExtension.php b/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/SyliusStateMachineAbstractionExtension.php index 55c674b3b44..d21a765168f 100644 --- a/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/SyliusStateMachineAbstractionExtension.php +++ b/src/Sylius/Abstraction/StateMachine/src/DependencyInjection/SyliusStateMachineAbstractionExtension.php @@ -27,7 +27,7 @@ public function load(array $configs, ContainerBuilder $container): void $loader = new XmlFileLoader($container, new FileLocator(dirname(__DIR__, 2) . '/config/')); $loader->load('services.xml'); - $container->setParameter('sylius_abstraction.state_machine.default_adapter', $config['state_machine']['default_adapter']); - $container->setParameter('sylius_abstraction.state_machine.graphs_to_adapters_mapping', $config['state_machine']['graphs_to_adapters_mapping']); + $container->setParameter('sylius_abstraction.state_machine.default_adapter', $config['default_adapter']); + $container->setParameter('sylius_abstraction.state_machine.graphs_to_adapters_mapping', $config['graphs_to_adapters_mapping']); } } diff --git a/src/Sylius/Abstraction/StateMachine/src/Exception/StateMachineExecutionException.php b/src/Sylius/Abstraction/StateMachine/src/Exception/StateMachineExecutionException.php index 5a87112ab0d..0e183e1d26d 100644 --- a/src/Sylius/Abstraction/StateMachine/src/Exception/StateMachineExecutionException.php +++ b/src/Sylius/Abstraction/StateMachine/src/Exception/StateMachineExecutionException.php @@ -13,6 +13,6 @@ namespace Sylius\Abstraction\StateMachine\Exception; -class StateMachineExecutionException extends \Exception +class StateMachineExecutionException extends \RuntimeException { } diff --git a/src/Sylius/Abstraction/StateMachine/src/StateMachineInterface.php b/src/Sylius/Abstraction/StateMachine/src/StateMachineInterface.php index 4d1398903a1..2d493e4a203 100644 --- a/src/Sylius/Abstraction/StateMachine/src/StateMachineInterface.php +++ b/src/Sylius/Abstraction/StateMachine/src/StateMachineInterface.php @@ -35,4 +35,14 @@ public function apply(object $subject, string $graphName, string $transition, ar * @return array */ public function getEnabledTransitions(object $subject, string $graphName): array; + + /** + * @throws StateMachineExecutionException + */ + public function getTransitionFromState(object $subject, string $graphName, string $fromState): ?string; + + /** + * @throws StateMachineExecutionException + */ + public function getTransitionToState(object $subject, string $graphName, string $toState): ?string; } diff --git a/src/Sylius/Abstraction/StateMachine/src/SymfonyWorkflowAdapter.php b/src/Sylius/Abstraction/StateMachine/src/SymfonyWorkflowAdapter.php index fd02e73a3a2..4309a12e36f 100644 --- a/src/Sylius/Abstraction/StateMachine/src/SymfonyWorkflowAdapter.php +++ b/src/Sylius/Abstraction/StateMachine/src/SymfonyWorkflowAdapter.php @@ -61,4 +61,26 @@ function (SymfonyWorkflowTransition $transition): TransitionInterface { $enabledTransitions, ); } + + public function getTransitionFromState(object $subject, string $graphName, string $fromState): ?string + { + foreach ($this->getEnabledTransitions($subject, $graphName) as $transition) { + if ($transition->getFroms() !== null && in_array($fromState, $transition->getFroms(), true)) { + return $transition->getName(); + } + } + + return null; + } + + public function getTransitionToState(object $subject, string $graphName, string $toState): ?string + { + foreach ($this->getEnabledTransitions($subject, $graphName) as $transition) { + if ($transition->getTos() !== null && in_array($toState, $transition->getTos(), true)) { + return $transition->getName(); + } + } + + return null; + } } diff --git a/src/Sylius/Abstraction/StateMachine/src/WinzouStateMachineAdapter.php b/src/Sylius/Abstraction/StateMachine/src/WinzouStateMachineAdapter.php index de87878892b..d8575114545 100644 --- a/src/Sylius/Abstraction/StateMachine/src/WinzouStateMachineAdapter.php +++ b/src/Sylius/Abstraction/StateMachine/src/WinzouStateMachineAdapter.php @@ -42,21 +42,79 @@ public function apply(object $subject, string $graphName, string $transition, ar } public function getEnabledTransitions(object $subject, string $graphName): array + { + $stateMachine = $this->getStateMachine($subject, $graphName); + + return array_filter( + $this->getAllTransitions($stateMachine), + fn (TransitionInterface $transition) => $this->can($subject, $graphName, $transition->getName()), + ); + } + + /** + * @return array + */ + private function getAllTransitions(\SM\StateMachine\StateMachineInterface $stateMachine): array { try { - $transitions = $this->getStateMachine($subject, $graphName)->getPossibleTransitions(); - } catch (SMException $exception) { + $transitionsConfig = $this->getConfig($stateMachine)['transitions']; + } catch (\ReflectionException $exception) { throw new StateMachineExecutionException($exception->getMessage(), $exception->getCode(), $exception); } - return array_map( - fn (string $transition) => new Transition($transition, null, null), - $transitions, - ); + $transitions = []; + + foreach ($transitionsConfig as $transitionName => $transitionConfig) { + $froms = $transitionConfig['from']; + $tos = array($transitionConfig['to']); + $transitions[] = new Transition($transitionName, $froms, $tos); + } + + return $transitions; + } + + /** + * @throws \ReflectionException + * + * @return array{transitions: array, to: string}>} + */ + private function getConfig(\SM\StateMachine\StateMachineInterface $stateMachine): array + { + $reflection = new \ReflectionClass($stateMachine); + $configProperty = $reflection->getProperty('config'); + $configProperty->setAccessible(true); + + return $configProperty->getValue($stateMachine); + } + + public function getTransitionFromState(object $subject, string $graphName, string $fromState): ?string + { + foreach ($this->getEnabledTransitions($subject, $graphName) as $transition) { + if ($transition->getFroms() !== null && in_array($fromState, $transition->getFroms(), true)) { + return $transition->getName(); + } + } + + return null; + } + + public function getTransitionToState(object $subject, string $graphName, string $toState): ?string + { + foreach ($this->getEnabledTransitions($subject, $graphName) as $transition) { + if ($transition->getTos() !== null && in_array($toState, $transition->getTos(), true)) { + return $transition->getName(); + } + } + + return null; } private function getStateMachine(object $subject, string $graphName): \SM\StateMachine\StateMachineInterface { - return $this->winzouStateMachineFactory->get($subject, $graphName); + try { + return $this->winzouStateMachineFactory->get($subject, $graphName); + } catch (SMException $exception) { + throw new StateMachineExecutionException($exception->getMessage(), $exception->getCode(), $exception); + } } } diff --git a/src/Sylius/Abstraction/StateMachine/tests/Functional/DependencyInjection/ConfigurationTest.php b/src/Sylius/Abstraction/StateMachine/tests/Functional/DependencyInjection/ConfigurationTest.php index 3e65cbb2fdf..c92a412b769 100644 --- a/src/Sylius/Abstraction/StateMachine/tests/Functional/DependencyInjection/ConfigurationTest.php +++ b/src/Sylius/Abstraction/StateMachine/tests/Functional/DependencyInjection/ConfigurationTest.php @@ -28,18 +28,13 @@ public function it_allows_to_configure_a_default_state_machine_adapter(): void $this->assertProcessedConfigurationEquals( [ [ - 'state_machine' => [ - 'default_adapter' => 'symfony_workflow', - ], + 'default_adapter' => 'symfony_workflow', ], ], [ - 'state_machine' => [ - 'default_adapter' => 'symfony_workflow', - 'graphs_to_adapters_mapping' => [], - ], + 'default_adapter' => 'symfony_workflow', + 'graphs_to_adapters_mapping' => [], ], - 'state_machine', ); } @@ -49,24 +44,19 @@ public function it_allows_to_configure_the_state_machines_adapters_mapping(): vo $this->assertProcessedConfigurationEquals( [ [ - 'state_machine' => [ - 'graphs_to_adapters_mapping' => [ - 'order' => 'symfony_workflow', - 'payment' => 'winzou_state_machine', - ], - ], - ], - ], - [ - 'state_machine' => [ - 'default_adapter' => 'winzou_state_machine', 'graphs_to_adapters_mapping' => [ 'order' => 'symfony_workflow', 'payment' => 'winzou_state_machine', ], ], ], - 'state_machine', + [ + 'default_adapter' => 'winzou_state_machine', + 'graphs_to_adapters_mapping' => [ + 'order' => 'symfony_workflow', + 'payment' => 'winzou_state_machine', + ], + ], ); } diff --git a/src/Sylius/Abstraction/StateMachine/tests/Unit/CompositeStateMachineTest.php b/src/Sylius/Abstraction/StateMachine/tests/Unit/CompositeStateMachineTest.php index e91cae83ddd..861da446d9c 100644 --- a/src/Sylius/Abstraction/StateMachine/tests/Unit/CompositeStateMachineTest.php +++ b/src/Sylius/Abstraction/StateMachine/tests/Unit/CompositeStateMachineTest.php @@ -78,6 +78,24 @@ public function testItReturnsEnabledTransitionsUsingMappedAdapter(): void $this->assertSame(['some_transition'], $stateMachine->getEnabledTransitions(new \stdClass(), 'another_graph')); } + public function testItReturnsTransitionFromStateUsingMappedAdapter(): void + { + $this->someStateMachineAdapter->expects($this->never())->method('getTransitionFromState'); + $this->anotherStateMachineAdapter->expects($this->once())->method('getTransitionFromState')->willReturn('some_transition'); + + $stateMachine = $this->createTestSubject(); + $this->assertSame('some_transition', $stateMachine->getTransitionFromState(new \stdClass(), 'another_graph', 'some_state')); + } + + public function testItReturnsTransitionToStateUsingMappedAdapter(): void + { + $this->someStateMachineAdapter->expects($this->never())->method('getTransitionToState'); + $this->anotherStateMachineAdapter->expects($this->once())->method('getTransitionToState')->willReturn('some_transition'); + + $stateMachine = $this->createTestSubject(); + $this->assertSame('some_transition', $stateMachine->getTransitionToState(new \stdClass(), 'another_graph', 'some_state')); + } + private function createTestSubject(mixed ...$arguments): StateMachineInterface { return new CompositeStateMachine(...array_replace([ diff --git a/src/Sylius/Abstraction/StateMachine/tests/Unit/SymfonyWorkflowAdapterTest.php b/src/Sylius/Abstraction/StateMachine/tests/Unit/SymfonyWorkflowAdapterTest.php index 41320697bfe..c60c83ab702 100644 --- a/src/Sylius/Abstraction/StateMachine/tests/Unit/SymfonyWorkflowAdapterTest.php +++ b/src/Sylius/Abstraction/StateMachine/tests/Unit/SymfonyWorkflowAdapterTest.php @@ -16,7 +16,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException; -use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Abstraction\StateMachine\SymfonyWorkflowAdapter; use Symfony\Component\Workflow\Exception\InvalidArgumentException; use Symfony\Component\Workflow\Registry; @@ -150,13 +149,126 @@ public function testItConvertsWorkflowExceptionsToCustomOnGetEnabledTransitions( $this->createTestSubject()->getEnabledTransitions($subject, $graphName); } - private function createTestSubject(): StateMachineInterface + /** + * @dataProvider itReturnsTransitionsToForGivenTransitionProvider + */ + public function testItReturnsTransitionsToForGivenTransition(?string $expectedToStateTransition, string $requestedTransition): void + { + $subject = new \stdClass(); + $graphName = 'graph_name'; + + $someTransition = $this->createSampleTransition( + name: 'some_transition', + froms: ['from_1', 'from_2'], + tos: ['to_1'], + ); + $anotherTransition = $this->createSampleTransition( + name: 'another_transition', + froms: ['from_1', 'from_3'], + tos: ['to_1', 'to_2'], + ); + + $workflow = $this->createMock(Workflow::class); + $workflow->method('getEnabledTransitions')->with($subject)->willReturn([$someTransition, $anotherTransition]); + + $this->symfonyWorkflowRegistry->method('get')->with($subject, $graphName)->willReturn($workflow); + + $testSubject = $this->createTestSubject(); + + $this->assertSame( + $expectedToStateTransition, + $testSubject->getTransitionToState($subject, $graphName, $requestedTransition), + ); + } + + /** + * @return iterable + */ + public static function itReturnsTransitionsToForGivenTransitionProvider(): iterable + { + yield 'it returns first transition for to_1' => [ + 'expectedToStateTransition' => 'some_transition', + 'requestedTransition' => 'to_1', + ]; + yield ' it returns second transition for to_2' => [ + 'expectedToStateTransition' => 'another_transition', + 'requestedTransition' => 'to_2', + ]; + yield 'it returns null for to_3' => [ + 'expectedToStateTransition' => null, + 'requestedTransition' => 'to_3', + ]; + } + + /** + * @dataProvider itReturnsTransitionsFromForGivenTransitionProvider + */ + public function testItReturnsTransitionsFromForGivenTransition(?string $expectedFromStateTransition, string $requestedTransition): void + { + $subject = new \stdClass(); + $graphName = 'graph_name'; + + $someTransition = $this->createSampleTransition( + name: 'some_transition', + froms: ['from_1', 'from_2'], + tos: ['to_1'], + ); + $anotherTransition = $this->createSampleTransition( + name: 'another_transition', + froms: ['from_1', 'from_3'], + tos: ['to_1', 'to_2'], + ); + + $workflow = $this->createMock(Workflow::class); + $workflow->method('getEnabledTransitions')->with($subject)->willReturn([$someTransition, $anotherTransition]); + + $this->symfonyWorkflowRegistry->method('get')->with($subject, $graphName)->willReturn($workflow); + + $testSubject = $this->createTestSubject(); + + $this->assertSame( + $expectedFromStateTransition, + $testSubject->getTransitionFromState($subject, $graphName, $requestedTransition), + ); + } + + /** + * @return iterable + */ + public static function itReturnsTransitionsFromForGivenTransitionProvider(): iterable + { + yield 'it returns first transition for from_1' => [ + 'expectedFromStateTransition' => 'some_transition', + 'requestedTransition' => 'from_1', + ]; + yield 'it returns first transition for from_2' => [ + 'expectedFromStateTransition' => 'some_transition', + 'requestedTransition' => 'from_2', + ]; + yield 'it returns second transition for from_3' => [ + 'expectedFromStateTransition' => 'another_transition', + 'requestedTransition' => 'from_3', + ]; + yield 'it returns null for from_4' => [ + 'expectedFromStateTransition' => null, + 'requestedTransition' => 'from_4', + ]; + } + + private function createTestSubject(): SymfonyWorkflowAdapter { return new SymfonyWorkflowAdapter($this->symfonyWorkflowRegistry); } - private function createSampleTransition(): SymfonyWorkflowTransition + private function createSampleTransition(mixed ...$arguments): SymfonyWorkflowTransition { - return new SymfonyWorkflowTransition('transition', ['from'], ['to']); + return new SymfonyWorkflowTransition(...array_replace( + [ + 'name' => 'transition', + 'froms' => ['from'], + 'tos' => ['to'], + ], + $arguments, + )); } } diff --git a/src/Sylius/Abstraction/StateMachine/tests/Unit/WinzouStateMachineAdapterTest.php b/src/Sylius/Abstraction/StateMachine/tests/Unit/WinzouStateMachineAdapterTest.php index 69cad9556a5..897540f896e 100644 --- a/src/Sylius/Abstraction/StateMachine/tests/Unit/WinzouStateMachineAdapterTest.php +++ b/src/Sylius/Abstraction/StateMachine/tests/Unit/WinzouStateMachineAdapterTest.php @@ -17,7 +17,7 @@ use PHPUnit\Framework\TestCase; use SM\Factory\FactoryInterface; use SM\SMException; -use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use SM\StateMachine\StateMachine as WinzouStateMachine; use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException; use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; @@ -27,14 +27,26 @@ final class WinzouStateMachineAdapterTest extends TestCase /** @var FactoryInterface&MockObject */ private FactoryInterface $winzouStateMachineFactory; - /** @var WinzouStateMachineInterface&MockObject */ - private WinzouStateMachineInterface $winzouStateMachine; + /** @var WinzouStateMachine&MockObject */ + private WinzouStateMachine $winzouStateMachine; protected function setUp(): void { parent::setUp(); - $this->winzouStateMachine = $this->createMock(WinzouStateMachineInterface::class); + $this->winzouStateMachine = $this->createMock(WinzouStateMachine::class); + $this->setStateMachineConfig($this->winzouStateMachine, [ + 'transitions' => [ + 'transition' => [ + 'from' => ['from_state'], + 'to' => 'to_state', + ], + 'another_transition' => [ + 'from' => ['another_from_state'], + 'to' => 'another_to_state', + ], + ], + ]); $this->winzouStateMachineFactory = $this->createMock(FactoryInterface::class); $this->winzouStateMachineFactory @@ -67,17 +79,20 @@ public function testItAppliesTransition(): void public function testItReturnsEnabledTransitions(): void { - $this->winzouStateMachine->method('getPossibleTransitions')->willReturn(['transition']); - $subject = new \stdClass(); $graphName = 'graph_name'; + $this->winzouStateMachine->method('can')->willReturnMap([ + ['transition', true], + ['another_transition', false], + ]); + $transitions = $this->createTestSubject()->getEnabledTransitions($subject, $graphName); $this->assertCount(1, $transitions); $this->assertSame('transition', $transitions[0]->getName()); - $this->assertNull($transitions[0]->getFroms()); - $this->assertNull($transitions[0]->getTos()); + $this->assertSame(['from_state'], $transitions[0]->getFroms()); + $this->assertSame(['to_state'], $transitions[0]->getTos()); } public function testItConvertsWorkflowExceptionsToCustomOnesOnCan(): void @@ -118,8 +133,67 @@ public function testItConvertsWorkflowExceptionsToCustomOnGetEnabledTransitions( $this->createTestSubject()->getEnabledTransitions($subject, $graphName); } + public function testItReturnsTransitionsToForGivenTransition(): void + { + $this->setStateMachineConfig($this->winzouStateMachine, [ + 'transitions' => [ + 'transition_to_state' => [ + 'from' => ['from_state'], + 'to' => 'to_state', + ], + ], + ]); + + $this->winzouStateMachine->method('can')->willReturn(true); + + $this->winzouStateMachineFactory = $this->createMock(FactoryInterface::class); + $this->winzouStateMachineFactory->method('get')->willReturn($this->winzouStateMachine); + + $stateMachine = $this->createTestSubject(); + + $this->assertSame( + 'transition_to_state', + $stateMachine->getTransitionToState(new \stdClass(), 'graph_name', 'to_state'), + ); + } + + public function testItReturnsTransitionsFromForGivenTransition(): void + { + $this->setStateMachineConfig($this->winzouStateMachine, [ + 'transitions' => [ + 'transition_from_state' => [ + 'from' => ['from_state'], + 'to' => 'to_state', + ], + ], + ]); + + $this->winzouStateMachine->method('can')->willReturn(true); + + $this->winzouStateMachineFactory = $this->createMock(FactoryInterface::class); + $this->winzouStateMachineFactory->method('get')->willReturn($this->winzouStateMachine); + + $stateMachine = $this->createTestSubject(); + + $this->assertSame( + 'transition_from_state', + $stateMachine->getTransitionFromState(new \stdClass(), 'graph_name', 'from_state'), + ); + } + private function createTestSubject(): StateMachineInterface { return new WinzouStateMachineAdapter($this->winzouStateMachineFactory); } + + /** + * @param array $config + */ + private function setStateMachineConfig(WinzouStateMachine $stateMachine, array $config): void + { + $reflection = new \ReflectionClass($stateMachine); + $configProperty = $reflection->getProperty('config'); + $configProperty->setAccessible(true); + $configProperty->setValue($stateMachine, $config); + } } diff --git a/src/Sylius/Bundle/AdminBundle/Event/OrderShowMenuBuilderEvent.php b/src/Sylius/Bundle/AdminBundle/Event/OrderShowMenuBuilderEvent.php index a8ccb5c22d3..9f6e1f9f59b 100644 --- a/src/Sylius/Bundle/AdminBundle/Event/OrderShowMenuBuilderEvent.php +++ b/src/Sylius/Bundle/AdminBundle/Event/OrderShowMenuBuilderEvent.php @@ -15,7 +15,8 @@ use Knp\Menu\FactoryInterface; use Knp\Menu\ItemInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent; use Sylius\Component\Core\Model\OrderInterface; @@ -25,9 +26,21 @@ public function __construct( FactoryInterface $factory, ItemInterface $menu, private OrderInterface $order, - private StateMachineInterface $stateMachine, + private WinzouStateMachineInterface|StateMachineInterface $stateMachine, ) { parent::__construct($factory, $menu); + + if ($this->stateMachine instanceof WinzouStateMachineInterface) { + trigger_deprecation( + 'sylius/admin-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the fourth argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + WinzouStateMachineInterface::class, + StateMachineInterface::class, + ), + ); + } } public function getOrder(): OrderInterface @@ -35,7 +48,7 @@ public function getOrder(): OrderInterface return $this->order; } - public function getStateMachine(): StateMachineInterface + public function getStateMachine(): WinzouStateMachineInterface|StateMachineInterface { return $this->stateMachine; } diff --git a/src/Sylius/Bundle/AdminBundle/Menu/OrderShowMenuBuilder.php b/src/Sylius/Bundle/AdminBundle/Menu/OrderShowMenuBuilder.php index 69dd5825db7..2e9fab85bae 100644 --- a/src/Sylius/Bundle/AdminBundle/Menu/OrderShowMenuBuilder.php +++ b/src/Sylius/Bundle/AdminBundle/Menu/OrderShowMenuBuilder.php @@ -16,6 +16,8 @@ use Knp\Menu\FactoryInterface; use Knp\Menu\ItemInterface; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\AdminBundle\Event\OrderShowMenuBuilderEvent; use Sylius\Component\Order\OrderTransitions; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -28,9 +30,21 @@ final class OrderShowMenuBuilder public function __construct( private FactoryInterface $factory, private EventDispatcherInterface $eventDispatcher, - private StateMachineFactoryInterface $stateMachineFactory, + private StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory, private CsrfTokenManagerInterface $csrfTokenManager, - ) { + ) + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + trigger_deprecation( + 'sylius/admin-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the third argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + StateMachineFactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function createMenu(array $options): ItemInterface @@ -50,25 +64,24 @@ public function createMenu(array $options): ItemInterface ]) ->setAttribute('type', 'link') ->setLabel('sylius.ui.history') - ->setLabelAttribute('icon', 'history') - ; + ->setLabelAttribute('icon', 'history'); - $stateMachine = $this->stateMachineFactory->get($order, OrderTransitions::GRAPH); - if ($stateMachine->can(OrderTransitions::TRANSITION_CANCEL)) { + $stateMachine = $this->getStateMachine(); + + if ($stateMachine->can($order, OrderTransitions::GRAPH, OrderTransitions::TRANSITION_CANCEL)) { $menu ->addChild('cancel', [ 'route' => 'sylius_admin_order_cancel', 'routeParameters' => [ 'id' => $order->getId(), - '_csrf_token' => $this->csrfTokenManager->getToken((string) $order->getId())->getValue(), + '_csrf_token' => $this->csrfTokenManager->getToken((string)$order->getId())->getValue(), ], ]) ->setAttribute('type', 'transition') ->setAttribute('confirmation', true) ->setLabel('sylius.ui.cancel') ->setLabelAttribute('icon', 'ban') - ->setLabelAttribute('color', 'yellow') - ; + ->setLabelAttribute('color', 'yellow'); } $this->eventDispatcher->dispatch( @@ -78,4 +91,13 @@ public function createMenu(array $options): ItemInterface return $menu; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/AdminBundle/Resources/config/services/menu.xml b/src/Sylius/Bundle/AdminBundle/Resources/config/services/menu.xml index 65fe8a65e61..ce9c1a4d239 100644 --- a/src/Sylius/Bundle/AdminBundle/Resources/config/services/menu.xml +++ b/src/Sylius/Bundle/AdminBundle/Resources/config/services/menu.xml @@ -36,7 +36,7 @@ - + diff --git a/src/Sylius/Bundle/AdminBundle/spec/Event/OrderShowMenuBuilderEventSpec.php b/src/Sylius/Bundle/AdminBundle/spec/Event/OrderShowMenuBuilderEventSpec.php index 0add8569761..254901ab40b 100644 --- a/src/Sylius/Bundle/AdminBundle/spec/Event/OrderShowMenuBuilderEventSpec.php +++ b/src/Sylius/Bundle/AdminBundle/spec/Event/OrderShowMenuBuilderEventSpec.php @@ -16,7 +16,8 @@ use Knp\Menu\FactoryInterface; use Knp\Menu\ItemInterface; use PhpSpec\ObjectBehavior; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\UiBundle\Menu\Event\MenuBuilderEvent; use Sylius\Component\Core\Model\OrderInterface; @@ -26,7 +27,7 @@ function let( FactoryInterface $factory, ItemInterface $menu, OrderInterface $order, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $this->beConstructedWith($factory, $menu, $order, $stateMachine); } @@ -41,8 +42,18 @@ function it_has_an_order(OrderInterface $order): void $this->getOrder()->shouldReturn($order); } - function it_has_a_state_machine(StateMachineInterface $stateMachine): void + function it_has_a_state_machine(WinzouStateMachineInterface $stateMachine): void { $this->getStateMachine()->shouldReturn($stateMachine); } + + function it_allows_to_pass_the_new_state_machine_abstraction( + FactoryInterface $factory, + ItemInterface $menu, + OrderInterface $order, + StateMachineInterface $newStateMachineAbstraction + ): void { + $this->beConstructedWith($factory, $menu, $order, $newStateMachineAbstraction); + $this->getStateMachine()->shouldReturn($newStateMachineAbstraction); + } } diff --git a/src/Sylius/Bundle/ApiBundle/Applicator/OrderStateMachineTransitionApplicator.php b/src/Sylius/Bundle/ApiBundle/Applicator/OrderStateMachineTransitionApplicator.php index 6fc061b9106..b371d46f196 100644 --- a/src/Sylius/Bundle/ApiBundle/Applicator/OrderStateMachineTransitionApplicator.php +++ b/src/Sylius/Bundle/ApiBundle/Applicator/OrderStateMachineTransitionApplicator.php @@ -14,14 +14,27 @@ namespace Sylius\Bundle\ApiBundle\Applicator; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Order\OrderTransitions; /** @experimental */ final class OrderStateMachineTransitionApplicator implements OrderStateMachineTransitionApplicatorInterface { - public function __construct(private StateMachineFactoryInterface $stateMachineFactory) + public function __construct(private StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory) { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + StateMachineFactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function cancel(OrderInterface $data): OrderInterface @@ -33,7 +46,15 @@ public function cancel(OrderInterface $data): OrderInterface private function applyTransition(OrderInterface $order, string $transition): void { - $stateMachine = $this->stateMachineFactory->get($order, OrderTransitions::GRAPH); - $stateMachine->apply($transition); + $this->getStateMachine()->apply($order, OrderTransitions::GRAPH, $transition); + } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; } } diff --git a/src/Sylius/Bundle/ApiBundle/Applicator/PaymentStateMachineTransitionApplicator.php b/src/Sylius/Bundle/ApiBundle/Applicator/PaymentStateMachineTransitionApplicator.php index 67dc0f7a623..704c5bd9ea6 100644 --- a/src/Sylius/Bundle/ApiBundle/Applicator/PaymentStateMachineTransitionApplicator.php +++ b/src/Sylius/Bundle/ApiBundle/Applicator/PaymentStateMachineTransitionApplicator.php @@ -14,14 +14,27 @@ namespace Sylius\Bundle\ApiBundle\Applicator; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Payment\Model\PaymentInterface; use Sylius\Component\Payment\PaymentTransitions; /** @experimental */ final class PaymentStateMachineTransitionApplicator implements PaymentStateMachineTransitionApplicatorInterface { - public function __construct(private StateMachineFactoryInterface $stateMachineFactory) + public function __construct(private StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory) { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + StateMachineFactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function complete(PaymentInterface $data): PaymentInterface @@ -33,7 +46,15 @@ public function complete(PaymentInterface $data): PaymentInterface private function applyTransition(PaymentInterface $payment, string $transition): void { - $stateMachine = $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH); - $stateMachine->apply($transition); + $this->getStateMachine()->apply($payment, PaymentTransitions::GRAPH, $transition); + } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; } } diff --git a/src/Sylius/Bundle/ApiBundle/Applicator/ProductReviewStateMachineTransitionApplicator.php b/src/Sylius/Bundle/ApiBundle/Applicator/ProductReviewStateMachineTransitionApplicator.php index 6b4105371fe..8829f9ed693 100644 --- a/src/Sylius/Bundle/ApiBundle/Applicator/ProductReviewStateMachineTransitionApplicator.php +++ b/src/Sylius/Bundle/ApiBundle/Applicator/ProductReviewStateMachineTransitionApplicator.php @@ -14,14 +14,27 @@ namespace Sylius\Bundle\ApiBundle\Applicator; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\ProductReviewTransitions; use Sylius\Component\Review\Model\ReviewInterface; /** @experimental */ final class ProductReviewStateMachineTransitionApplicator implements ProductReviewStateMachineTransitionApplicatorInterface { - public function __construct(private StateMachineFactoryInterface $stateMachineFactory) + public function __construct(private StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory) { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + StateMachineFactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function accept(ReviewInterface $data): ReviewInterface @@ -40,7 +53,15 @@ public function reject(ReviewInterface $data): ReviewInterface private function applyTransition(ReviewInterface $review, string $transition): void { - $stateMachine = $this->stateMachineFactory->get($review, ProductReviewTransitions::GRAPH); - $stateMachine->apply($transition); + $this->getStateMachine()->apply($review, ProductReviewTransitions::GRAPH, $transition); + } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; } } diff --git a/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ChoosePaymentMethodHandler.php b/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ChoosePaymentMethodHandler.php index 5632c1c6e89..25493648eb2 100644 --- a/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ChoosePaymentMethodHandler.php +++ b/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ChoosePaymentMethodHandler.php @@ -14,6 +14,8 @@ namespace Sylius\Bundle\ApiBundle\CommandHandler\Checkout; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\ApiBundle\Changer\PaymentMethodChangerInterface; use Sylius\Bundle\ApiBundle\Command\Checkout\ChoosePaymentMethod; use Sylius\Bundle\ApiBundle\Exception\PaymentMethodCannotBeChangedException; @@ -33,9 +35,20 @@ public function __construct( private OrderRepositoryInterface $orderRepository, private PaymentMethodRepositoryInterface $paymentMethodRepository, private PaymentRepositoryInterface $paymentRepository, - private FactoryInterface $stateMachineFactory, + private FactoryInterface|StateMachineInterface $stateMachineFactory, private PaymentMethodChangerInterface $paymentMethodChanger, ) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the fourth argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function __invoke(ChoosePaymentMethod $choosePaymentMethod): OrderInterface @@ -64,21 +77,27 @@ public function __invoke(ChoosePaymentMethod $choosePaymentMethod): OrderInterfa Assert::notNull($payment, 'Can not find payment with given identifier.'); if ($cart->getState() === OrderInterface::STATE_CART) { - $stateMachine = $this->stateMachineFactory->get($cart, OrderCheckoutTransitions::GRAPH); - + $stateMachine = $this->getStateMachine(); Assert::true( - $stateMachine->can( - OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT, - ), + $stateMachine->can($cart, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT), 'Order cannot have payment method assigned.', ); $payment->setMethod($paymentMethod); - $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT); + $stateMachine->apply($cart, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_PAYMENT); return $cart; } throw new PaymentMethodCannotBeChangedException(); } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ChooseShippingMethodHandler.php b/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ChooseShippingMethodHandler.php index 264f5f1c88d..64f4b3e2e7d 100644 --- a/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ChooseShippingMethodHandler.php +++ b/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ChooseShippingMethodHandler.php @@ -14,6 +14,8 @@ namespace Sylius\Bundle\ApiBundle\CommandHandler\Checkout; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\ApiBundle\Command\Checkout\ChooseShippingMethod; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\ShippingMethodInterface; @@ -33,8 +35,19 @@ public function __construct( private ShippingMethodRepositoryInterface $shippingMethodRepository, private ShipmentRepositoryInterface $shipmentRepository, private ShippingMethodEligibilityCheckerInterface $eligibilityChecker, - private FactoryInterface $stateMachineFactory, + private FactoryInterface|StateMachineInterface $stateMachineFactory, ) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the fifth argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function __invoke(ChooseShippingMethod $chooseShippingMethod): OrderInterface @@ -44,10 +57,9 @@ public function __invoke(ChooseShippingMethod $chooseShippingMethod): OrderInter Assert::notNull($cart, 'Cart has not been found.'); - $stateMachine = $this->stateMachineFactory->get($cart, OrderCheckoutTransitions::GRAPH); - + $stateMachine = $this->getStateMachine(); Assert::true( - $stateMachine->can(OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING), + $stateMachine->can($cart, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING), 'Order cannot have shipment method assigned.', ); @@ -66,8 +78,17 @@ public function __invoke(ChooseShippingMethod $chooseShippingMethod): OrderInter ); $shipment->setMethod($shippingMethod); - $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING); + $stateMachine->apply($cart, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SELECT_SHIPPING); return $cart; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/CompleteOrderHandler.php b/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/CompleteOrderHandler.php index 05fcd848bf2..d07cf12f145 100644 --- a/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/CompleteOrderHandler.php +++ b/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/CompleteOrderHandler.php @@ -14,7 +14,9 @@ namespace Sylius\Bundle\ApiBundle\CommandHandler\Checkout; use SM\Factory\FactoryInterface; -use SM\SMException; +use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\ApiBundle\Command\Cart\InformAboutCartRecalculation; use Sylius\Bundle\ApiBundle\Command\Checkout\CompleteOrder; use Sylius\Bundle\ApiBundle\CommandHandler\Checkout\Exception\OrderTotalHasChangedException; @@ -33,15 +35,26 @@ final class CompleteOrderHandler implements MessageHandlerInterface { public function __construct( private OrderRepositoryInterface $orderRepository, - private FactoryInterface $stateMachineFactory, + private FactoryInterface|StateMachineInterface $stateMachineFactory, private MessageBusInterface $commandBus, private MessageBusInterface $eventBus, private OrderPromotionsIntegrityCheckerInterface $orderPromotionsIntegrityChecker, ) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the second argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } /** - * @throws SMException + * @throws StateMachineExecutionException * @throws OrderTotalHasChangedException */ public function __invoke(CompleteOrder $completeOrder): OrderInterface @@ -73,17 +86,25 @@ public function __invoke(CompleteOrder $completeOrder): OrderInterface throw new OrderTotalHasChangedException(); } - $stateMachine = $this->stateMachineFactory->get($cart, OrderCheckoutTransitions::GRAPH); - + $stateMachine = $this->getStateMachine(); Assert::true( - $stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE), + $stateMachine->can($cart, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_COMPLETE), sprintf('Order with %s token cannot be completed.', $orderTokenValue), ); - $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_COMPLETE); + $stateMachine->apply($cart, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_COMPLETE); $this->eventBus->dispatch(new OrderCompleted($cart->getTokenValue()), [new DispatchAfterCurrentBusStamp()]); return $cart; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ShipShipmentHandler.php b/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ShipShipmentHandler.php index 7cd2ec9a7a9..b9a482b0dae 100644 --- a/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ShipShipmentHandler.php +++ b/src/Sylius/Bundle/ApiBundle/CommandHandler/Checkout/ShipShipmentHandler.php @@ -14,6 +14,8 @@ namespace Sylius\Bundle\ApiBundle\CommandHandler\Checkout; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\ApiBundle\Command\Checkout\SendShipmentConfirmationEmail; use Sylius\Bundle\ApiBundle\Command\Checkout\ShipShipment; use Sylius\Component\Core\Model\ShipmentInterface; @@ -29,9 +31,20 @@ final class ShipShipmentHandler implements MessageHandlerInterface { public function __construct( private ShipmentRepositoryInterface $shipmentRepository, - private FactoryInterface $stateMachineFactory, + private FactoryInterface|StateMachineInterface $stateMachineFactory, private MessageBusInterface $eventBus, ) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the second argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function __invoke(ShipShipment $shipShipment): ShipmentInterface @@ -45,17 +58,25 @@ public function __invoke(ShipShipment $shipShipment): ShipmentInterface $shipment->setTracking($shipShipment->trackingCode); } - $stateMachine = $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH); - + $stateMachine = $this->getStateMachine(); Assert::true( - $stateMachine->can(ShipmentTransitions::TRANSITION_SHIP), + $stateMachine->can($shipment, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_SHIP), 'This shipment cannot be completed.', ); - $stateMachine->apply(ShipmentTransitions::TRANSITION_SHIP); + $stateMachine->apply($shipment, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_SHIP); $this->eventBus->dispatch(new SendShipmentConfirmationEmail($shipShipment->shipmentId), [new DispatchAfterCurrentBusStamp()]); return $shipment; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/ApiBundle/Modifier/OrderAddressModifier.php b/src/Sylius/Bundle/ApiBundle/Modifier/OrderAddressModifier.php index e555b699b71..49e94d59b89 100644 --- a/src/Sylius/Bundle/ApiBundle/Modifier/OrderAddressModifier.php +++ b/src/Sylius/Bundle/ApiBundle/Modifier/OrderAddressModifier.php @@ -14,6 +14,8 @@ namespace Sylius\Bundle\ApiBundle\Modifier; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\ApiBundle\Mapper\AddressMapperInterface; use Sylius\Component\Core\Model\AddressInterface; use Sylius\Component\Core\Model\OrderInterface; @@ -23,9 +25,20 @@ final class OrderAddressModifier implements OrderAddressModifierInterface { public function __construct( - private StateMachineFactoryInterface $stateMachineFactory, + private StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory, private AddressMapperInterface $addressMapper, ) { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + StateMachineFactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function modify( @@ -33,10 +46,9 @@ public function modify( ?AddressInterface $billingAddress, ?AddressInterface $shippingAddress = null, ): OrderInterface { - $stateMachine = $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH); - + $stateMachine = $this->getStateMachine(); Assert::true( - $stateMachine->can(OrderCheckoutTransitions::TRANSITION_ADDRESS), + $stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_ADDRESS), sprintf('Order with %s token cannot be addressed.', $order->getTokenValue()), ); @@ -59,7 +71,7 @@ public function modify( $this->setBillingAddress($order, $oldBillingAddress, $billingAddress); $this->setShippingAddress($order, $oldShippingAddress, $shippingAddress); - $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_ADDRESS); + $stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_ADDRESS); return $order; } @@ -91,4 +103,13 @@ private function setShippingAddress( $order->setShippingAddress($shippingAddress); } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/app/config.yaml b/src/Sylius/Bundle/ApiBundle/Resources/config/app/config.yaml index c4e035d4525..a11fd645ec8 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/app/config.yaml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/app/config.yaml @@ -55,6 +55,7 @@ api_platform: # Sylius exception to status code mapping SM\SMException: 422 + Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException: 422 Sylius\Bundle\ApiBundle\Exception\CannotRemoveCurrentlyLoggedInUser: 422 Sylius\Bundle\ApiBundle\Exception\ChannelCannotBeRemoved: 422 Sylius\Bundle\ApiBundle\Exception\ChannelPricingChannelCodeMismatchException: 422 diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml index f53d6bd933f..0e374118433 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services.xml @@ -143,7 +143,7 @@ - + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml index 7590e7a7ac7..1b20c04aac8 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/command_handlers.xml @@ -77,7 +77,7 @@ - + @@ -86,7 +86,7 @@ - + @@ -94,7 +94,7 @@ - + @@ -104,7 +104,7 @@ - + diff --git a/src/Sylius/Bundle/ApiBundle/Resources/config/services/validator.xml b/src/Sylius/Bundle/ApiBundle/Resources/config/services/validator.xml index be8a50873bd..fbec7925b10 100644 --- a/src/Sylius/Bundle/ApiBundle/Resources/config/services/validator.xml +++ b/src/Sylius/Bundle/ApiBundle/Resources/config/services/validator.xml @@ -48,7 +48,7 @@ - + diff --git a/src/Sylius/Bundle/ApiBundle/Validator/Constraints/CheckoutCompletionValidator.php b/src/Sylius/Bundle/ApiBundle/Validator/Constraints/CheckoutCompletionValidator.php index 73217bf3247..e947f9abafa 100644 --- a/src/Sylius/Bundle/ApiBundle/Validator/Constraints/CheckoutCompletionValidator.php +++ b/src/Sylius/Bundle/ApiBundle/Validator/Constraints/CheckoutCompletionValidator.php @@ -14,6 +14,9 @@ namespace Sylius\Bundle\ApiBundle\Validator\Constraints; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\TransitionInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\ApiBundle\Command\OrderTokenValueAwareInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\OrderCheckoutTransitions; @@ -30,8 +33,19 @@ class CheckoutCompletionValidator extends ConstraintValidator */ public function __construct( private OrderRepositoryInterface $orderRepository, - private FactoryInterface $stateMachineFactory, + private FactoryInterface|StateMachineInterface $stateMachineFactory, ) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/api-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the second argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function validate(mixed $value, Constraint $constraint): void @@ -41,20 +55,35 @@ public function validate(mixed $value, Constraint $constraint): void /** @var CheckoutCompletion $constraint */ Assert::isInstanceOf($constraint, CheckoutCompletion::class); + /** @var OrderInterface|null $order */ $order = $this->orderRepository->findOneBy(['tokenValue' => $value->getOrderTokenValue()]); - /** @var OrderInterface $order */ Assert::isInstanceOf($order, OrderInterface::class); - $stateMachine = $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH); + $stateMachine = $this->getStateMachine(); - if ($stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE)) { + if ($stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_COMPLETE)) { return; } $this->context->addViolation($constraint->message, [ - '%currentState%' => $stateMachine->getState(), - '%possibleTransitions%' => implode(', ', $stateMachine->getPossibleTransitions()), + '%currentState%' => $order->getCheckoutState(), + '%possibleTransitions%' => implode( + ', ', + array_map( + fn (TransitionInterface $transition) => $transition->getName(), + $stateMachine->getEnabledTransitions($order, OrderCheckoutTransitions::GRAPH), + ), + ), ]); } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/ApiBundle/composer.json b/src/Sylius/Bundle/ApiBundle/composer.json index 4f596a596b6..d4b83d8d1c6 100644 --- a/src/Sylius/Bundle/ApiBundle/composer.json +++ b/src/Sylius/Bundle/ApiBundle/composer.json @@ -72,8 +72,8 @@ }, "autoload-dev": { "psr-4": { + "spec\\Sylius\\Bundle\\ApiBundle\\": "spec/", "Sylius\\Bundle\\ApiBundle\\Application\\": "Tests/Application/src/", - "Sylius\\Bundle\\ApiBundle\\spec\\": "spec/", "Sylius\\Bundle\\ApiBundle\\Tests\\": "Tests/" } }, diff --git a/src/Sylius/Bundle/ApiBundle/spec/Applicator/OrderStateMachineTransitionApplicatorSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Applicator/OrderStateMachineTransitionApplicatorSpec.php index 9a25b5a4e22..0fa96296ba1 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/Applicator/OrderStateMachineTransitionApplicatorSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/Applicator/OrderStateMachineTransitionApplicatorSpec.php @@ -15,7 +15,8 @@ use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; -use SM\StateMachine\StateMachine; +use SM\StateMachine\StateMachine as WinzouStateMachine; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Order\OrderTransitions; @@ -29,11 +30,22 @@ function let(StateMachineFactoryInterface $stateMachineFactory) function it_cancels_order( StateMachineFactoryInterface $stateMachineFactory, OrderInterface $order, - StateMachine $stateMachine, + WinzouStateMachine $stateMachine, ): void { $stateMachineFactory->get($order, OrderTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->apply(OrderTransitions::TRANSITION_CANCEL)->shouldBeCalled(); $this->cancel($order); } + + function it_uses_the_new_state_machine_abstraction_if_passed( + StateMachineInterface $stateMachine, + OrderInterface $order, + ): void { + $this->beConstructedWith($stateMachine); + + $stateMachine->apply($order, OrderTransitions::GRAPH, OrderTransitions::TRANSITION_CANCEL)->shouldBeCalled(); + + $this->cancel($order); + } } diff --git a/src/Sylius/Bundle/ApiBundle/spec/Applicator/PaymentStateMachineTransitionApplicatorSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Applicator/PaymentStateMachineTransitionApplicatorSpec.php index 6d2514ae6fe..9462b27496d 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/Applicator/PaymentStateMachineTransitionApplicatorSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/Applicator/PaymentStateMachineTransitionApplicatorSpec.php @@ -15,7 +15,8 @@ use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; -use SM\StateMachine\StateMachine; +use SM\StateMachine\StateMachine as WinzouStateMachine; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Payment\PaymentTransitions; @@ -29,11 +30,22 @@ function let(StateMachineFactoryInterface $stateMachineFactory) function it_completes_payment( StateMachineFactoryInterface $stateMachineFactory, PaymentInterface $payment, - StateMachine $stateMachine, + WinzouStateMachine $stateMachine, ): void { $stateMachineFactory->get($payment, PaymentTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->apply(PaymentTransitions::TRANSITION_COMPLETE)->shouldBeCalled(); $this->complete($payment); } + + function it_uses_the_new_state_machine_abstraction_if_passed( + StateMachineInterface $stateMachine, + PaymentInterface $payment, + ): void { + $this->beConstructedWith($stateMachine); + + $stateMachine->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_COMPLETE)->shouldBeCalled(); + + $this->complete($payment); + } } diff --git a/src/Sylius/Bundle/ApiBundle/spec/Applicator/ProductReviewStateMachineTransitionApplicatorSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Applicator/ProductReviewStateMachineTransitionApplicatorSpec.php index 134f7fd5317..7f6a17154db 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/Applicator/ProductReviewStateMachineTransitionApplicatorSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/Applicator/ProductReviewStateMachineTransitionApplicatorSpec.php @@ -15,7 +15,8 @@ use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; -use SM\StateMachine\StateMachine; +use SM\StateMachine\StateMachine as WinzouStateMachine; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Component\Core\ProductReviewTransitions; use Sylius\Component\Review\Model\ReviewInterface; @@ -29,7 +30,7 @@ function let(StateMachineFactoryInterface $stateMachineFactory) public function it_accepts_product_review( StateMachineFactoryInterface $stateMachineFactory, ReviewInterface $review, - StateMachine $stateMachine, + WinzouStateMachine $stateMachine, ): void { $stateMachineFactory->get($review, ProductReviewTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->apply(ProductReviewTransitions::TRANSITION_ACCEPT)->shouldBeCalled(); @@ -37,14 +38,36 @@ public function it_accepts_product_review( $this->accept($review); } + public function it_uses_the_new_state_machine_abstraction_if_passed_while_accepting_a_product_review( + StateMachineInterface $stateMachine, + ReviewInterface $review, + ): void { + $this->beConstructedWith($stateMachine); + + $stateMachine->apply($review, ProductReviewTransitions::GRAPH, ProductReviewTransitions::TRANSITION_ACCEPT)->shouldBeCalled(); + + $this->accept($review); + } + public function it_rejects_product_review( StateMachineFactoryInterface $stateMachineFactory, ReviewInterface $review, - StateMachine $stateMachine, + WinzouStateMachine $stateMachine, ): void { $stateMachineFactory->get($review, ProductReviewTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->apply(ProductReviewTransitions::TRANSITION_REJECT)->shouldBeCalled(); $this->reject($review); } + + public function it_uses_the_new_state_machine_abstraction_if_passed_while_rejecting_a_product_review( + StateMachineInterface $stateMachine, + ReviewInterface $review, + ): void { + $this->beConstructedWith($stateMachine); + + $stateMachine->apply($review, ProductReviewTransitions::GRAPH, ProductReviewTransitions::TRANSITION_REJECT)->shouldBeCalled(); + + $this->reject($review); + } } diff --git a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ChoosePaymentMethodHandlerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ChoosePaymentMethodHandlerSpec.php index bc3019eb2dd..d9e8d1b8952 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ChoosePaymentMethodHandlerSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ChoosePaymentMethodHandlerSpec.php @@ -16,7 +16,8 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\ApiBundle\Changer\PaymentMethodChangerInterface; use Sylius\Bundle\ApiBundle\Command\Checkout\ChoosePaymentMethod; use Sylius\Bundle\ApiBundle\Exception\PaymentMethodCannotBeChangedException; @@ -55,7 +56,7 @@ function it_assigns_chosen_payment_method_to_specified_payment_while_checkout( OrderInterface $cart, PaymentInterface $payment, PaymentMethodInterface $paymentMethod, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $choosePaymentMethod = new ChoosePaymentMethod('CASH_ON_DELIVERY_METHOD'); $choosePaymentMethod->setOrderTokenValue('ORDERTOKEN'); @@ -82,6 +83,41 @@ function it_assigns_chosen_payment_method_to_specified_payment_while_checkout( $this($choosePaymentMethod)->shouldReturn($cart); } + function it_uses_the_new_state_machine_abstraction_if_passed( + OrderRepositoryInterface $orderRepository, + PaymentMethodRepositoryInterface $paymentMethodRepository, + PaymentRepositoryInterface $paymentRepository, + StateMachineInterface $stateMachine, + PaymentMethodChangerInterface $paymentMethodChanger, + OrderInterface $cart, + PaymentInterface $payment, + PaymentMethodInterface $paymentMethod, + ): void { + $this->beConstructedWith($orderRepository, $paymentMethodRepository, $paymentRepository, $stateMachine, $paymentMethodChanger); + + $choosePaymentMethod = new ChoosePaymentMethod('CASH_ON_DELIVERY_METHOD'); + $choosePaymentMethod->setOrderTokenValue('ORDERTOKEN'); + $choosePaymentMethod->setSubresourceId('123'); + + $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($cart); + $cart->getCheckoutState()->willReturn(OrderCheckoutStates::STATE_SHIPPING_SELECTED); + + $stateMachine->can($cart, OrderCheckoutTransitions::GRAPH, 'select_payment')->willReturn(true); + $stateMachine->apply($cart, OrderCheckoutTransitions::GRAPH, 'select_payment')->shouldBeCalled(); + + $paymentMethodRepository->findOneBy(['code' => 'CASH_ON_DELIVERY_METHOD'])->willReturn($paymentMethod); + + $cart->getId()->willReturn('111'); + + $paymentRepository->findOneByOrderId('123', '111')->willReturn($payment); + + $cart->getState()->willReturn(OrderInterface::STATE_CART); + + $payment->setMethod($paymentMethod)->shouldBeCalled(); + + $this($choosePaymentMethod)->shouldReturn($cart); + } + function it_throws_an_exception_if_order_with_given_token_has_not_been_found( OrderRepositoryInterface $orderRepository, PaymentInterface $payment, @@ -105,7 +141,7 @@ function it_throws_an_exception_if_order_cannot_have_payment_selected( PaymentMethodRepositoryInterface $paymentMethodRepository, FactoryInterface $stateMachineFactory, OrderInterface $cart, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, PaymentInterface $payment, ): void { $choosePaymentMethod = new ChoosePaymentMethod('CASH_ON_DELIVERY_METHOD'); @@ -134,7 +170,7 @@ function it_throws_an_exception_if_payment_method_with_given_code_has_not_been_f PaymentMethodRepositoryInterface $paymentMethodRepository, FactoryInterface $stateMachineFactory, OrderInterface $cart, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, PaymentInterface $payment, ): void { $choosePaymentMethod = new ChoosePaymentMethod('CASH_ON_DELIVERY_METHOD'); @@ -165,7 +201,7 @@ function it_throws_an_exception_if_ordered_payment_has_not_been_found( FactoryInterface $stateMachineFactory, OrderInterface $cart, PaymentMethodInterface $paymentMethod, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $choosePaymentMethod = new ChoosePaymentMethod('CASH_ON_DELIVERY_METHOD'); $choosePaymentMethod->setOrderTokenValue('ORDERTOKEN'); diff --git a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ChooseShippingMethodHandlerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ChooseShippingMethodHandlerSpec.php index 7e2f3665d8c..2afd1a50f44 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ChooseShippingMethodHandlerSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ChooseShippingMethodHandlerSpec.php @@ -17,7 +17,8 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\ApiBundle\Command\Checkout\ChooseShippingMethod; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\ShipmentInterface; @@ -55,7 +56,7 @@ function it_assigns_choosen_shipping_method_to_specified_shipment( OrderInterface $cart, ShippingMethodInterface $shippingMethod, ShipmentInterface $shipment, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $chooseShippingMethod = new ChooseShippingMethod('DHL_SHIPPING_METHOD'); $chooseShippingMethod->setOrderTokenValue('ORDERTOKEN'); @@ -82,6 +83,48 @@ function it_assigns_choosen_shipping_method_to_specified_shipment( $this($chooseShippingMethod)->shouldReturn($cart); } + function it_uses_the_new_state_machine_abstraction_if_passed( + OrderRepositoryInterface $orderRepository, + ShippingMethodRepositoryInterface $shippingMethodRepository, + ShipmentRepositoryInterface $shipmentRepository, + ShippingMethodEligibilityCheckerInterface $eligibilityChecker, + StateMachineInterface $stateMachine, + OrderInterface $cart, + ShippingMethodInterface $shippingMethod, + ShipmentInterface $shipment, + ): void { + $this->beConstructedWith( + $orderRepository, + $shippingMethodRepository, + $shipmentRepository, + $eligibilityChecker, + $stateMachine, + ); + + $chooseShippingMethod = new ChooseShippingMethod('DHL_SHIPPING_METHOD'); + $chooseShippingMethod->setOrderTokenValue('ORDERTOKEN'); + $chooseShippingMethod->setSubresourceId('123'); + + $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($cart); + + $stateMachine->can($cart, OrderCheckoutTransitions::GRAPH, 'select_shipping')->willReturn(true); + + $shippingMethodRepository->findOneBy(['code' => 'DHL_SHIPPING_METHOD'])->willReturn($shippingMethod); + + $cart->getShipments()->willReturn(new ArrayCollection([$shipment->getWrappedObject()])); + + $cart->getId()->willReturn('111'); + + $shipmentRepository->findOneByOrderId('123', '111')->willReturn($shipment); + + $eligibilityChecker->isEligible($shipment, $shippingMethod)->willReturn(true); + + $shipment->setMethod($shippingMethod)->shouldBeCalled(); + $stateMachine->apply($cart, OrderCheckoutTransitions::GRAPH, 'select_shipping')->shouldBeCalled(); + + $this($chooseShippingMethod)->shouldReturn($cart); + } + function it_throws_an_exception_if_shipping_method_is_not_eligible( OrderRepositoryInterface $orderRepository, ShippingMethodRepositoryInterface $shippingMethodRepository, @@ -91,7 +134,7 @@ function it_throws_an_exception_if_shipping_method_is_not_eligible( OrderInterface $cart, ShippingMethodInterface $shippingMethod, ShipmentInterface $shipment, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $chooseShippingMethod = new ChooseShippingMethod('DHL_SHIPPING_METHOD'); $chooseShippingMethod->setOrderTokenValue('ORDERTOKEN'); @@ -143,7 +186,7 @@ function it_throws_an_exception_if_order_cannot_have_shipping_selected( ShippingMethodRepositoryInterface $shippingMethodRepository, FactoryInterface $stateMachineFactory, OrderInterface $cart, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ShipmentInterface $shipment, ): void { $chooseShippingMethod = new ChooseShippingMethod('DHL_SHIPPING_METHOD'); @@ -169,7 +212,7 @@ function it_throws_an_exception_if_shipping_method_with_given_code_has_not_been_ ShippingMethodRepositoryInterface $shippingMethodRepository, FactoryInterface $stateMachineFactory, OrderInterface $cart, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ShipmentInterface $shipment, ): void { $chooseShippingMethod = new ChooseShippingMethod('DHL_SHIPPING_METHOD'); @@ -199,7 +242,7 @@ function it_throws_an_exception_if_ordered_shipment_has_not_been_found( FactoryInterface $stateMachineFactory, OrderInterface $cart, ShippingMethodInterface $shippingMethod, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $chooseShippingMethod = new ChooseShippingMethod('DHL_SHIPPING_METHOD'); $chooseShippingMethod->setOrderTokenValue('ORDERTOKEN'); diff --git a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/CompleteOrderHandlerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/CompleteOrderHandlerSpec.php index bc13df6b370..c2016e7942f 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/CompleteOrderHandlerSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/CompleteOrderHandlerSpec.php @@ -15,7 +15,8 @@ use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\ApiBundle\Command\Cart\InformAboutCartRecalculation; use Sylius\Bundle\ApiBundle\Command\Checkout\CompleteOrder; use Sylius\Bundle\ApiBundle\CommandHandler\Checkout\Exception\OrderTotalHasChangedException; @@ -47,7 +48,7 @@ function it_handles_order_completion_without_notes( FactoryInterface $stateMachineFactory, MessageBusInterface $eventBus, OrderPromotionsIntegrityCheckerInterface $orderPromotionsIntegrityChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, CustomerInterface $customer, ): void { @@ -80,12 +81,51 @@ function it_handles_order_completion_without_notes( $this($completeOrder)->shouldReturn($order); } + function it_uses_the_new_state_machine_abstraction_if_passed( + OrderRepositoryInterface $orderRepository, + StateMachineInterface $stateMachine, + MessageBusInterface $commandBus, + MessageBusInterface $eventBus, + OrderPromotionsIntegrityCheckerInterface $orderPromotionsIntegrityChecker, + OrderInterface $order, + CustomerInterface $customer, + ): void { + $this->beConstructedWith($orderRepository, $stateMachine, $commandBus, $eventBus, $orderPromotionsIntegrityChecker); + + $completeOrder = new CompleteOrder(); + $completeOrder->setOrderTokenValue('ORDERTOKEN'); + + $orderRepository->findOneBy(['tokenValue' => 'ORDERTOKEN'])->willReturn($order); + + $order->getCustomer()->willReturn($customer); + $order->getTotal()->willReturn(1500); + + $order->setNotes(null)->shouldNotBeCalled(); + + $orderPromotionsIntegrityChecker->check($order)->willReturn(null); + + $stateMachine->can($order, OrderCheckoutTransitions::GRAPH, 'complete')->willReturn(true); + $order->getTokenValue()->willReturn('COMPLETED_ORDER_TOKEN'); + + $stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, 'complete')->shouldBeCalled(); + + $orderCompleted = new OrderCompleted('COMPLETED_ORDER_TOKEN'); + + $eventBus + ->dispatch($orderCompleted, [new DispatchAfterCurrentBusStamp()]) + ->willReturn(new Envelope($orderCompleted)) + ->shouldBeCalled() + ; + + $this($completeOrder)->shouldReturn($order); + } + function it_handles_order_completion_with_notes( OrderRepositoryInterface $orderRepository, FactoryInterface $stateMachineFactory, MessageBusInterface $eventBus, OrderPromotionsIntegrityCheckerInterface $orderPromotionsIntegrityChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, CustomerInterface $customer, ): void { @@ -190,7 +230,7 @@ function it_throws_an_exception_if_order_cannot_be_completed( OrderRepositoryInterface $orderRepository, FactoryInterface $stateMachineFactory, OrderPromotionsIntegrityCheckerInterface $orderPromotionsIntegrityChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, CustomerInterface $customer, ): void { diff --git a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ShipShipmentHandlerSpec.php b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ShipShipmentHandlerSpec.php index c4298741dbc..c6ec85b3bae 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ShipShipmentHandlerSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/CommandHandler/Checkout/ShipShipmentHandlerSpec.php @@ -15,7 +15,8 @@ use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\ApiBundle\Command\Checkout\SendShipmentConfirmationEmail; use Sylius\Bundle\ApiBundle\Command\Checkout\ShipShipment; use Sylius\Component\Core\Model\ShipmentInterface; @@ -37,7 +38,7 @@ function let( function it_handles_shipping_without_tracking_number( ShipmentRepositoryInterface $shipmentRepository, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ShipmentInterface $shipment, FactoryInterface $stateMachineFactory, MessageBusInterface $eventBus, @@ -67,7 +68,7 @@ function it_handles_shipping_without_tracking_number( function it_handles_shipping_with_tracking_number( ShipmentRepositoryInterface $shipmentRepository, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ShipmentInterface $shipment, FactoryInterface $stateMachineFactory, MessageBusInterface $eventBus, @@ -95,6 +96,35 @@ function it_handles_shipping_with_tracking_number( $this($shipShipment)->shouldReturn($shipment); } + function it_uses_the_new_state_machine_abstraction_if_passed( + ShipmentRepositoryInterface $shipmentRepository, + StateMachineInterface $stateMachine, + MessageBusInterface $eventBus, + ShipmentInterface $shipment, + ): void { + $this->beConstructedWith($shipmentRepository, $stateMachine, $eventBus); + + $shipShipment = new ShipShipment('TRACK'); + $shipShipment->setShipmentId(123); + + $shipmentRepository->find(123)->willReturn($shipment); + + $shipment->setTracking('TRACK')->shouldBeCalled(); + + $stateMachine->can($shipment, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_SHIP)->willReturn(true); + $stateMachine->apply($shipment, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_SHIP)->shouldBeCalled(); + + $sendShipmentConfirmationEmail = new SendShipmentConfirmationEmail(123); + + $eventBus + ->dispatch($sendShipmentConfirmationEmail, [new DispatchAfterCurrentBusStamp()]) + ->willReturn(new Envelope($sendShipmentConfirmationEmail)) + ->shouldBeCalled() + ; + + $this($shipShipment)->shouldReturn($shipment); + } + function it_throws_an_exception_if_shipment_does_not_exist( ShipmentRepositoryInterface $shipmentRepository, ): void { @@ -111,7 +141,7 @@ function it_throws_an_exception_if_shipment_does_not_exist( function it_throws_an_exception_if_shipment_cannot_be_shipped( ShipmentRepositoryInterface $shipmentRepository, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ShipmentInterface $shipment, FactoryInterface $stateMachineFactory, ): void { diff --git a/src/Sylius/Bundle/ApiBundle/spec/Modifier/OrderAddressModifierSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Modifier/OrderAddressModifierSpec.php index b4dfae7b426..58b7a1b2d3b 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/Modifier/OrderAddressModifierSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/Modifier/OrderAddressModifierSpec.php @@ -16,7 +16,8 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\ApiBundle\Mapper\AddressMapperInterface; use Sylius\Component\Core\Model\AddressInterface; use Sylius\Component\Core\Model\ChannelInterface; @@ -37,7 +38,7 @@ function it_modifies_addresses_of_an_order_without_provided_shipping_address( AddressInterface $billingAddress, OrderInterface $order, ChannelInterface $channel, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $order->getTokenValue()->willReturn('ORDERTOKEN'); $order->getShippingAddress()->willReturn(null); @@ -61,7 +62,7 @@ function it_modifies_addresses_of_an_order_without_provided_billing_address( AddressInterface $shippingAddress, OrderInterface $order, ChannelInterface $channel, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $order->getTokenValue()->willReturn('ORDERTOKEN'); $order->getShippingAddress()->willReturn(null); @@ -86,7 +87,7 @@ function it_modifies_addresses_of_an_order( AddressInterface $shippingAddress, OrderInterface $order, ChannelInterface $channel, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $order->getTokenValue()->willReturn('ORDERTOKEN'); $order->getShippingAddress()->willReturn(null); @@ -105,6 +106,32 @@ function it_modifies_addresses_of_an_order( $this->modify($order, $billingAddress->getWrappedObject(), $shippingAddress->getWrappedObject(), 'r2d2@droid.com'); } + function it_uses_the_new_state_machine_abstraction_if_provided( + StateMachineInterface $stateMachine, + AddressMapperInterface $addressMapper, + AddressInterface $billingAddress, + AddressInterface $shippingAddress, + OrderInterface $order, + ChannelInterface $channel, + ): void { + $this->beConstructedWith($stateMachine, $addressMapper); + + $order->getTokenValue()->willReturn('ORDERTOKEN'); + $order->getShippingAddress()->willReturn(null); + $order->getBillingAddress()->willReturn(null); + $order->getChannel()->willReturn($channel); + + $channel->isShippingAddressInCheckoutRequired()->willReturn(false); + + $order->setBillingAddress($billingAddress)->shouldBeCalled(); + $order->setShippingAddress($shippingAddress)->shouldBeCalled(); + + $stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_ADDRESS)->willReturn(true); + $stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_ADDRESS)->shouldBeCalled(); + + $this->modify($order, $billingAddress->getWrappedObject(), $shippingAddress->getWrappedObject(), 'r2d2@droid.com'); + } + function it_updates_order_addresses( StateMachineFactoryInterface $stateMachineFactory, AddressMapperInterface $addressMapper, @@ -114,7 +141,7 @@ function it_updates_order_addresses( AddressInterface $oldShippingAddress, OrderInterface $order, ChannelInterface $channel, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $order->getTokenValue()->willReturn('ORDERTOKEN'); $order->getBillingAddress()->willReturn($oldBillingAddress); @@ -141,7 +168,7 @@ function it_throws_an_exception_if_order_cannot_be_addressed( AddressInterface $billingAddress, AddressInterface $shippingAddress, OrderInterface $order, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->willReturn($stateMachine); $stateMachine->can(OrderCheckoutTransitions::TRANSITION_ADDRESS)->willReturn(false); diff --git a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/CheckoutCompletionValidatorSpec.php b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/CheckoutCompletionValidatorSpec.php index 7c55691558f..6fb896eccbe 100644 --- a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/CheckoutCompletionValidatorSpec.php +++ b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/CheckoutCompletionValidatorSpec.php @@ -16,7 +16,8 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\Transition; use Sylius\Bundle\ApiBundle\Command\OrderTokenValueAwareInterface; use Sylius\Bundle\ApiBundle\Validator\Constraints\CheckoutCompletion; use Sylius\Component\Core\Model\OrderInterface; @@ -79,7 +80,7 @@ function it_does_nothing_if_order_can_be_completed( ExecutionContextInterface $executionContext, OrderInterface $order, OrderTokenValueAwareInterface $orderTokenValueAware, - StateMachineInterface $stateMachine, + WinzouStateMachineStub $stateMachine, ): void { $this->initialize($executionContext); @@ -105,8 +106,27 @@ function it_adds_violation_if_order_cannot_be_completed( ExecutionContextInterface $executionContext, OrderInterface $order, OrderTokenValueAwareInterface $orderTokenValueAware, - StateMachineInterface $stateMachine, + WinzouStateMachineStub $stateMachine, ): void { + $stateMachine->can('some_possible_transition')->willReturn(true); + $stateMachine->can('another_possible_transition')->willReturn(true); + $stateMachine->can('yet_another_possible_transition')->willReturn(false); + $this->setPropertyValue($stateMachine, 'config', [ + 'transitions' => [ + 'some_possible_transition' => [ + 'from' => [], + 'to' => '', + ], + 'another_possible_transition' => [ + 'from' => [], + 'to' => '', + ], + 'yet_another_possible_transition' => [ + 'from' => [], + 'to' => '', + ], + ] + ]); $this->initialize($executionContext); $orderTokenValueAware->getOrderTokenValue()->willReturn('xxx'); @@ -114,8 +134,44 @@ function it_adds_violation_if_order_cannot_be_completed( $stateMachineFactory->get($order, 'sylius_order_checkout')->willReturn($stateMachine); $stateMachine->can(OrderCheckoutTransitions::TRANSITION_COMPLETE)->willReturn(false); - $stateMachine->getState()->willReturn('some_state_that_does_not_allow_to_complete_order'); - $stateMachine->getPossibleTransitions()->willReturn(['some_possible_transition', 'another_possible_transition']); + + $order->getCheckoutState()->willReturn('some_state_that_does_not_allow_to_complete_order'); + + $executionContext + ->addViolation( + 'sylius.order.invalid_state_transition', + [ + '%currentState%' => 'some_state_that_does_not_allow_to_complete_order', + '%possibleTransitions%' => 'some_possible_transition, another_possible_transition', + ], + ) + ->shouldBeCalled() + ; + + $this->validate($orderTokenValueAware, new CheckoutCompletion()); + } + + function it_uses_the_new_state_machine_abstraction_if_passed( + OrderRepositoryInterface $orderRepository, + StateMachineInterface $stateMachine, + ExecutionContextInterface $executionContext, + OrderInterface $order, + OrderTokenValueAwareInterface $orderTokenValueAware, + ): void { + $this->beConstructedWith($orderRepository, $stateMachine); + + $this->initialize($executionContext); + + $orderTokenValueAware->getOrderTokenValue()->willReturn('xxx'); + $orderRepository->findOneBy(['tokenValue' => 'xxx'])->willReturn($order); + + $stateMachine->can($order, 'sylius_order_checkout', OrderCheckoutTransitions::TRANSITION_COMPLETE)->willReturn(false); + $stateMachine + ->getEnabledTransitions($order, 'sylius_order_checkout') + ->willReturn([new Transition('some_possible_transition', null, null), new Transition('another_possible_transition', null, null)]) + ; + + $order->getCheckoutState()->willReturn('some_state_that_does_not_allow_to_complete_order'); $executionContext ->addViolation( @@ -130,4 +186,12 @@ function it_adds_violation_if_order_cannot_be_completed( $this->validate($orderTokenValueAware, new CheckoutCompletion()); } + + private function setPropertyValue(object $object, string $property, mixed $value): void + { + $reflection = new \ReflectionClass(WinzouStateMachineStub::class); + $reflectionProperty = $reflection->getProperty($property); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($object, $value); + } } diff --git a/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/WinzouStateMachineStub.php b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/WinzouStateMachineStub.php new file mode 100644 index 00000000000..fb563d1add9 --- /dev/null +++ b/src/Sylius/Bundle/ApiBundle/spec/Validator/Constraints/WinzouStateMachineStub.php @@ -0,0 +1,21 @@ +stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/core-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the second argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function process(CatalogPromotionInterface $catalogPromotion): void { - $stateMachine = $this->stateMachineFactory->get($catalogPromotion, CatalogPromotionTransitions::GRAPH); + $stateMachine = $this->getStateMachine(); - if ($stateMachine->can(CatalogPromotionTransitions::TRANSITION_PROCESS)) { - $stateMachine->apply(CatalogPromotionTransitions::TRANSITION_PROCESS); + if ($stateMachine->can($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_PROCESS)) { + $stateMachine->apply($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_PROCESS); return; } if (!$this->catalogPromotionEligibilityChecker->isCatalogPromotionEligible($catalogPromotion)) { - if ($stateMachine->can(CatalogPromotionTransitions::TRANSITION_DEACTIVATE)) { - $stateMachine->apply(CatalogPromotionTransitions::TRANSITION_DEACTIVATE); + if ($stateMachine->can($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_DEACTIVATE)) { + $stateMachine->apply($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_DEACTIVATE); } return; } - if ($stateMachine->can(CatalogPromotionTransitions::TRANSITION_ACTIVATE)) { - $stateMachine->apply(CatalogPromotionTransitions::TRANSITION_ACTIVATE); + if ($stateMachine->can($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_ACTIVATE)) { + $stateMachine->apply($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_ACTIVATE); + } + } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); } + + return $this->stateMachineFactory; } } diff --git a/src/Sylius/Bundle/CoreBundle/Checkout/CheckoutResolver.php b/src/Sylius/Bundle/CoreBundle/Checkout/CheckoutResolver.php index 6b8f3ba2b33..62498cb9bf8 100644 --- a/src/Sylius/Bundle/CoreBundle/Checkout/CheckoutResolver.php +++ b/src/Sylius/Bundle/CoreBundle/Checkout/CheckoutResolver.php @@ -14,6 +14,8 @@ namespace Sylius\Bundle\CoreBundle\Checkout; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Order\Context\CartContextInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -29,8 +31,19 @@ public function __construct( private CartContextInterface $cartContext, private CheckoutStateUrlGeneratorInterface $urlGenerator, private RequestMatcherInterface $requestMatcher, - private FactoryInterface $stateMachineFactory, + private FactoryInterface|StateMachineInterface $stateMachineFactory, ) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/core-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the fourth argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public static function getSubscribedEvents(): array @@ -73,9 +86,7 @@ public function onKernelRequest(RequestEvent $event): void return; } - $stateMachine = $this->stateMachineFactory->get($order, $graph); - - if ($stateMachine->can($transition)) { + if ($this->getStateMachine()->can($order, $graph, $transition)) { return; } @@ -91,4 +102,13 @@ private function getRequestedTransition(Request $request): ?string { return $request->attributes->get('_sylius', [])['state_machine']['transition'] ?? null; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/CoreBundle/Fixture/Factory/OrderExampleFactory.php b/src/Sylius/Bundle/CoreBundle/Fixture/Factory/OrderExampleFactory.php index 2f70b81f1f7..464a0c00fc5 100644 --- a/src/Sylius/Bundle/CoreBundle/Fixture/Factory/OrderExampleFactory.php +++ b/src/Sylius/Bundle/CoreBundle/Fixture/Factory/OrderExampleFactory.php @@ -17,6 +17,8 @@ use Faker\Factory; use Faker\Generator; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\CoreBundle\Fixture\OptionsResolver\LazyOption; use Sylius\Component\Addressing\Model\CountryInterface; use Sylius\Component\Core\Checker\OrderPaymentMethodSelectionRequirementCheckerInterface; @@ -61,13 +63,25 @@ public function __construct( protected PaymentMethodRepositoryInterface $paymentMethodRepository, protected ShippingMethodRepositoryInterface $shippingMethodRepository, protected FactoryInterface $addressFactory, - protected StateMachineFactoryInterface $stateMachineFactory, + protected StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory, protected OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, protected OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, ) { $this->optionsResolver = new OptionsResolver(); $this->faker = Factory::create(); $this->configureOptions($this->optionsResolver); + + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + trigger_deprecation( + 'sylius/core-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the twelfth argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + StateMachineFactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function create(array $options = []): OrderInterface @@ -251,7 +265,7 @@ protected function completeCheckout(OrderInterface $order): void protected function applyCheckoutStateTransition(OrderInterface $order, string $transition): void { - $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH)->apply($transition); + $this->getStateMachine()->apply($order, OrderCheckoutTransitions::GRAPH, $transition); } protected function generateInvalidSkipMessage(string $type, string $channelCode): string @@ -281,21 +295,32 @@ protected function fulfillOrder(OrderInterface $order): void protected function completePayments(OrderInterface $order): void { + $stateMachine = $this->getStateMachine(); + foreach ($order->getPayments() as $payment) { - $stateMachine = $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH); - if ($stateMachine->can(PaymentTransitions::TRANSITION_COMPLETE)) { - $stateMachine->apply(PaymentTransitions::TRANSITION_COMPLETE); + if ($stateMachine->can($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_COMPLETE)) { + $stateMachine->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_COMPLETE); } } } protected function completeShipments(OrderInterface $order): void { + $stateMachine = $this->getStateMachine(); + foreach ($order->getShipments() as $shipment) { - $stateMachine = $this->stateMachineFactory->get($shipment, ShipmentTransitions::GRAPH); - if ($stateMachine->can(ShipmentTransitions::TRANSITION_SHIP)) { - $stateMachine->apply(ShipmentTransitions::TRANSITION_SHIP); + if ($stateMachine->can($shipment, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_SHIP)) { + $stateMachine->apply($shipment, ShipmentTransitions::GRAPH, ShipmentTransitions::TRANSITION_SHIP); } } } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/CoreBundle/Fixture/Factory/ProductReviewExampleFactory.php b/src/Sylius/Bundle/CoreBundle/Fixture/Factory/ProductReviewExampleFactory.php index 80595958869..b892bebc5de 100644 --- a/src/Sylius/Bundle/CoreBundle/Fixture/Factory/ProductReviewExampleFactory.php +++ b/src/Sylius/Bundle/CoreBundle/Fixture/Factory/ProductReviewExampleFactory.php @@ -16,10 +16,11 @@ use Faker\Factory; use Faker\Generator; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\CoreBundle\Fixture\OptionsResolver\LazyOption; use Sylius\Component\Core\Repository\CustomerRepositoryInterface; use Sylius\Component\Core\Repository\ProductRepositoryInterface; -use Sylius\Component\Resource\StateMachine\StateMachineInterface; use Sylius\Component\Review\Factory\ReviewFactoryInterface; use Sylius\Component\Review\Model\ReviewInterface; use Symfony\Component\OptionsResolver\Options; @@ -35,12 +36,24 @@ public function __construct( private ReviewFactoryInterface $productReviewFactory, private ProductRepositoryInterface $productRepository, private CustomerRepositoryInterface $customerRepository, - private FactoryInterface $stateMachineFactory, + private FactoryInterface|StateMachineInterface $stateMachineFactory, ) { $this->faker = Factory::create(); $this->optionsResolver = new OptionsResolver(); $this->configureOptions($this->optionsResolver); + + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/core-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the fourth argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function create(array $options = []): ReviewInterface @@ -95,12 +108,21 @@ private function getRandomStatus(): string private function applyReviewTransition(ReviewInterface $productReview, string $targetState): void { - /** @var StateMachineInterface $stateMachine */ - $stateMachine = $this->stateMachineFactory->get($productReview, 'sylius_product_review'); - $transition = $stateMachine->getTransitionToState($targetState); + $stateMachine = $this->getStateMachine(); + + $transition = $stateMachine->getTransitionToState($productReview, 'sylius_product_review', $targetState); if (null !== $transition) { - $stateMachine->apply($transition); + $stateMachine->apply($productReview, 'sylius_product_review', $transition); + } + } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); } + + return $this->stateMachineFactory; } } diff --git a/src/Sylius/Bundle/CoreBundle/Fixture/OrderFixture.php b/src/Sylius/Bundle/CoreBundle/Fixture/OrderFixture.php index e3db77c6fe3..c7d1e906ec5 100644 --- a/src/Sylius/Bundle/CoreBundle/Fixture/OrderFixture.php +++ b/src/Sylius/Bundle/CoreBundle/Fixture/OrderFixture.php @@ -17,6 +17,7 @@ use Faker\Factory; use Faker\Generator; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\CoreBundle\Fixture\Factory\OrderExampleFactory; use Sylius\Bundle\FixturesBundle\Fixture\AbstractFixture; use Sylius\Component\Core\Checker\OrderPaymentMethodSelectionRequirementCheckerInterface; @@ -52,7 +53,7 @@ public function __construct( PaymentMethodRepositoryInterface $paymentMethodRepository, ShippingMethodRepositoryInterface $shippingMethodRepository, FactoryInterface $addressFactory, - StateMachineFactoryInterface $stateMachineFactory, + StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory, OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, OrderExampleFactory $orderExampleFactory = null, diff --git a/src/Sylius/Bundle/CoreBundle/Fixture/PaymentFixture.php b/src/Sylius/Bundle/CoreBundle/Fixture/PaymentFixture.php index cdc3da413a4..8eee166c32b 100644 --- a/src/Sylius/Bundle/CoreBundle/Fixture/PaymentFixture.php +++ b/src/Sylius/Bundle/CoreBundle/Fixture/PaymentFixture.php @@ -17,6 +17,8 @@ use Faker\Factory; use Faker\Generator; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\FixturesBundle\Fixture\AbstractFixture; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Repository\PaymentRepositoryInterface; @@ -45,13 +47,25 @@ class PaymentFixture extends AbstractFixture */ public function __construct( private PaymentRepositoryInterface $paymentRepository, - private StateMachineFactoryInterface $stateMachineFactory, + private StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory, private ObjectManager $paymentManager, ) { $this->faker = Factory::create(); $this->optionsResolver = new OptionsResolver(); $this->configureOptions($this->optionsResolver); + + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + trigger_deprecation( + 'sylius/core-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the second argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + StateMachineFactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function getName(): string @@ -88,11 +102,7 @@ protected function configureOptionsNode(ArrayNodeDefinition $optionsNode): void private function completePayment(PaymentInterface $payment): void { - $this - ->stateMachineFactory - ->get($payment, PaymentTransitions::GRAPH) - ->apply(PaymentTransitions::TRANSITION_COMPLETE) - ; + $this->getStateMachine()->apply($payment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_COMPLETE); $this->paymentManager->persist($payment); } @@ -104,4 +114,13 @@ private function configureOptions(OptionsResolver $resolver): void ->setAllowedTypes('percentage_completed', 'int') ; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/app/config.yml b/src/Sylius/Bundle/CoreBundle/Resources/config/app/config.yml index 7a0307ee1b2..4fa7466f729 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/app/config.yml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/app/config.yml @@ -142,3 +142,14 @@ sonata_block: resources: ~ statistics: ~ taxon: ~ + +sylius_state_machine_abstraction: + graphs_to_adapters_mapping: + sylius_catalog_promotion: symfony_workflow + sylius_order: symfony_workflow + sylius_order_checkout: symfony_workflow + sylius_order_payment: symfony_workflow + sylius_order_shipping: symfony_workflow + sylius_payment: symfony_workflow + sylius_product_review: symfony_workflow + sylius_shipment: symfony_workflow diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services.xml index a9821eef097..f90a7d5e471 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services.xml @@ -205,7 +205,7 @@ - + %sylius_order.order_expiration_period% @@ -215,7 +215,7 @@ - + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures.xml index 69d95aa3494..83384bb41de 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures.xml @@ -199,7 +199,7 @@ - + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures_factories.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures_factories.xml index 8d23b38d77b..41e373fee14 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures_factories.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/fixtures_factories.xml @@ -121,7 +121,7 @@ - + @@ -178,14 +178,14 @@ - + - + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order.xml index c509686db15..d7af0ca2566 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order.xml @@ -15,82 +15,82 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_checkout.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_checkout.xml index c735e7d0fc7..05d719706ce 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_checkout.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_checkout.xml @@ -15,36 +15,36 @@ - - - - - + + + + + - + - + - - + + - + - + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_payment.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_payment.xml index ebe31e0306a..35b417a7791 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_payment.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_payment.xml @@ -17,12 +17,12 @@ - + - + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_shipping.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_shipping.xml index 52ebaaa6c82..ed39c7d41dd 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_shipping.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/order_shipping.xml @@ -17,7 +17,7 @@ - + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/payment.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/payment.xml index 776704890ed..45010ba4fe2 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/payment.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/payment.xml @@ -17,16 +17,16 @@ - - + + - - - - + + + + diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/shipment.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/shipment.xml index b9569bea4af..0096fdb0974 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/shipment.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/listeners/workflow/shipment.xml @@ -19,7 +19,7 @@ @@ -27,7 +27,7 @@ diff --git a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_resolvers.xml b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_resolvers.xml index 639b582a24e..ea05a773da6 100644 --- a/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_resolvers.xml +++ b/src/Sylius/Bundle/CoreBundle/Resources/config/services/state_resolvers.xml @@ -16,18 +16,18 @@ - + - + - + - + diff --git a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/OrderCheckoutWorkflowTest.php b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/OrderCheckoutWorkflowTest.php index 3ce50228327..02690bad61e 100644 --- a/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/OrderCheckoutWorkflowTest.php +++ b/src/Sylius/Bundle/CoreBundle/Tests/Functional/StateMachine/OrderCheckoutWorkflowTest.php @@ -21,7 +21,6 @@ use Sylius\Component\Core\Model\Customer; use Sylius\Component\Core\Model\Order; use Sylius\Component\Core\Model\OrderInterface; -use Sylius\Component\Core\OrderShippingStates; use Sylius\Component\Core\Repository\PromotionRepositoryInterface; use Sylius\Component\Resource\Repository\RepositoryInterface; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -216,7 +215,6 @@ private function createOrderWithCheckoutState(string $checkoutState = 'cart'): O $order->setChannel($channel); $order->setCustomer($customer); $order->setCheckoutState($checkoutState); - $order->setShippingState(OrderShippingStates::STATE_READY); return $order; } diff --git a/src/Sylius/Bundle/CoreBundle/spec/CatalogPromotion/Processor/CatalogPromotionStateProcessorSpec.php b/src/Sylius/Bundle/CoreBundle/spec/CatalogPromotion/Processor/CatalogPromotionStateProcessorSpec.php index 60e33c4d353..a6f593c85fd 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/CatalogPromotion/Processor/CatalogPromotionStateProcessorSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/CatalogPromotion/Processor/CatalogPromotionStateProcessorSpec.php @@ -15,7 +15,8 @@ use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\CoreBundle\CatalogPromotion\Checker\CatalogPromotionEligibilityCheckerInterface; use Sylius\Bundle\CoreBundle\CatalogPromotion\Processor\CatalogPromotionStateProcessorInterface; use Sylius\Component\Core\Model\CatalogPromotionInterface; @@ -39,7 +40,7 @@ function it_processes_a_catalog_promotion( CatalogPromotionEligibilityCheckerInterface $catalogPromotionEligibilityChecker, CatalogPromotionInterface $catalogPromotion, FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $stateMachineFactory->get($catalogPromotion, CatalogPromotionTransitions::GRAPH)->willReturn($stateMachine); @@ -54,11 +55,29 @@ function it_processes_a_catalog_promotion( $this->process($catalogPromotion); } + function it_uses_the_new_state_machine_adapter_if_passed( + CatalogPromotionEligibilityCheckerInterface $catalogPromotionEligibilityChecker, + CatalogPromotionInterface $catalogPromotion, + StateMachineInterface $stateMachine, + ): void { + $this->beConstructedWith($catalogPromotionEligibilityChecker, $stateMachine); + + $catalogPromotionEligibilityChecker->isCatalogPromotionEligible($catalogPromotion)->willReturn(true); + + $stateMachine->can($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_PROCESS)->willReturn(true); + $stateMachine->apply($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_PROCESS)->shouldBeCalled(); + + $stateMachine->apply($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_ACTIVATE)->shouldNotBeCalled(); + $stateMachine->apply($catalogPromotion, CatalogPromotionTransitions::GRAPH, CatalogPromotionTransitions::TRANSITION_DEACTIVATE)->shouldNotBeCalled(); + + $this->process($catalogPromotion); + } + function it_activates_a_catalog_promotion( CatalogPromotionEligibilityCheckerInterface $catalogPromotionEligibilityChecker, CatalogPromotionInterface $catalogPromotion, FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $stateMachineFactory->get($catalogPromotion, CatalogPromotionTransitions::GRAPH)->willReturn($stateMachine); @@ -78,7 +97,7 @@ function it_deactivates_a_catalog_promotion( CatalogPromotionEligibilityCheckerInterface $catalogPromotionEligibilityChecker, CatalogPromotionInterface $catalogPromotion, FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $stateMachineFactory->get($catalogPromotion, CatalogPromotionTransitions::GRAPH)->willReturn($stateMachine); diff --git a/src/Sylius/Bundle/CoreBundle/spec/Checkout/CheckoutResolverSpec.php b/src/Sylius/Bundle/CoreBundle/spec/Checkout/CheckoutResolverSpec.php index 4a6a41ee383..b30d7a27905 100644 --- a/src/Sylius/Bundle/CoreBundle/spec/Checkout/CheckoutResolverSpec.php +++ b/src/Sylius/Bundle/CoreBundle/spec/Checkout/CheckoutResolverSpec.php @@ -16,7 +16,8 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Bundle\CoreBundle\Checkout\CheckoutStateUrlGeneratorInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Order\Context\CartContextInterface; @@ -160,7 +161,7 @@ function it_does_nothing_when_the_requested_transition_can_be_applied( CartContextInterface $cartContext, OrderInterface $order, FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $request = new Request([], [], [ '_sylius' => ['state_machine' => ['graph' => 'test_graph', 'transition' => 'test_transition']], @@ -183,7 +184,7 @@ function it_redirects_when_the_requested_transition_cannot_be_applied( CartContextInterface $cartContext, OrderInterface $order, FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, CheckoutStateUrlGeneratorInterface $urlGenerator, ): void { $request = new Request([], [], [ @@ -201,4 +202,29 @@ function it_redirects_when_the_requested_transition_cannot_be_applied( $this->onKernelRequest($event); } + + function it_uses_the_new_state_machine_abstraction_if_passed( + CartContextInterface $cartContext, + CheckoutStateUrlGeneratorInterface $urlGenerator, + RequestMatcherInterface $requestMatcher, + StateMachineInterface $stateMachine, + RequestEvent $event, + OrderInterface $order, + ): void { + $this->beConstructedWith($cartContext, $urlGenerator, $requestMatcher, $stateMachine); + + $request = new Request([], [], [ + '_sylius' => ['state_machine' => ['graph' => 'test_graph', 'transition' => 'test_transition']], + ]); + $event->isMainRequest()->willReturn(true); + $event->getRequest()->willReturn($request); + $requestMatcher->matches($request)->willReturn(true); + $cartContext->getCart()->willReturn($order); + $order->isEmpty()->willReturn(false); + $stateMachine->can($order, 'test_graph', 'test_transition')->willReturn(false); + $urlGenerator->generateForOrderCheckoutState($order)->willReturn('/target-url'); + $event->setResponse(Argument::type(RedirectResponse::class))->shouldBeCalled(); + + $this->onKernelRequest($event); + } } 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 faf8dd3ab62..4b0f3afa52b 100644 --- a/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml +++ b/src/Sylius/Bundle/CoreBundle/test/config/packages/state_machine.yaml @@ -55,7 +55,6 @@ framework: to: unpublished sylius_state_machine_abstraction: - state_machine: - default_adapter: 'winzou_state_machine' - graphs_to_adapters_mapping: - app_blog_post: 'symfony_workflow' + default_adapter: 'winzou_state_machine' + graphs_to_adapters_mapping: + app_blog_post: 'symfony_workflow' diff --git a/src/Sylius/Bundle/PayumBundle/Extension/UpdatePaymentStateExtension.php b/src/Sylius/Bundle/PayumBundle/Extension/UpdatePaymentStateExtension.php index b9e8e5ffa13..73c216d70d8 100644 --- a/src/Sylius/Bundle/PayumBundle/Extension/UpdatePaymentStateExtension.php +++ b/src/Sylius/Bundle/PayumBundle/Extension/UpdatePaymentStateExtension.php @@ -19,16 +19,25 @@ use Payum\Core\Request\GetStatusInterface; use Payum\Core\Request\Notify; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Bundle\PayumBundle\Request\GetStatus; use Sylius\Component\Payment\Model\PaymentInterface; use Sylius\Component\Payment\PaymentTransitions; -use Sylius\Component\Resource\StateMachine\StateMachineInterface; -use Webmozart\Assert\Assert; final class UpdatePaymentStateExtension implements ExtensionInterface { - public function __construct(private FactoryInterface $factory) + public function __construct(private FactoryInterface|StateMachineInterface $factory) { + trigger_deprecation( + 'sylius/payum-bundle', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); } public function onPreExecute(Context $context): void @@ -84,13 +93,19 @@ public function onPostExecute(Context $context): void private function updatePaymentState(PaymentInterface $payment, string $nextState): void { - $stateMachine = $this->factory->get($payment, PaymentTransitions::GRAPH); + $stateMachine = $this->getStateMachine(); - /** @var StateMachineInterface $stateMachine */ - Assert::isInstanceOf($stateMachine, StateMachineInterface::class); + if (null !== $transition = $stateMachine->getTransitionToState($payment, PaymentTransitions::GRAPH, $nextState)) { + $stateMachine->apply($payment, PaymentTransitions::GRAPH, $transition); + } + } - if (null !== $transition = $stateMachine->getTransitionToState($nextState)) { - $stateMachine->apply($transition); + private function getStateMachine(): StateMachineInterface + { + if ($this->factory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->factory); } + + return $this->factory; } } diff --git a/src/Sylius/Bundle/PayumBundle/Resources/config/services/extension.xml b/src/Sylius/Bundle/PayumBundle/Resources/config/services/extension.xml index 145578cd383..3e0ab4f616a 100644 --- a/src/Sylius/Bundle/PayumBundle/Resources/config/services/extension.xml +++ b/src/Sylius/Bundle/PayumBundle/Resources/config/services/extension.xml @@ -8,7 +8,7 @@ - + diff --git a/src/Sylius/Component/Core/Payment/Provider/OrderPaymentProvider.php b/src/Sylius/Component/Core/Payment/Provider/OrderPaymentProvider.php index 157ea92c007..47f795b2324 100644 --- a/src/Sylius/Component/Core/Payment/Provider/OrderPaymentProvider.php +++ b/src/Sylius/Component/Core/Payment/Provider/OrderPaymentProvider.php @@ -14,6 +14,8 @@ namespace Sylius\Component\Core\Payment\Provider; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; @@ -22,7 +24,6 @@ use Sylius\Component\Payment\Factory\PaymentFactoryInterface; use Sylius\Component\Payment\PaymentTransitions; use Sylius\Component\Payment\Resolver\DefaultPaymentMethodResolverInterface; -use Sylius\Component\Resource\StateMachine\StateMachineInterface; use Webmozart\Assert\Assert; final class OrderPaymentProvider implements OrderPaymentProviderInterface @@ -30,8 +31,19 @@ final class OrderPaymentProvider implements OrderPaymentProviderInterface public function __construct( private DefaultPaymentMethodResolverInterface $defaultPaymentMethodResolver, private PaymentFactoryInterface $paymentFactory, - private StateMachineFactoryInterface $stateMachineFactory, + private StateMachineFactoryInterface|StateMachineInterface $stateMachineFactory, ) { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + trigger_deprecation( + 'sylius/core', + '1.13', + sprintf( + 'Passing an instance of "%s" as the third argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + StateMachineFactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function provideOrderPayment(OrderInterface $order, string $targetState): ?PaymentInterface @@ -88,12 +100,20 @@ private function applyRequiredTransition(PaymentInterface $payment, string $targ return; } - /** @var StateMachineInterface $stateMachine */ - $stateMachine = $this->stateMachineFactory->get($payment, PaymentTransitions::GRAPH); + $stateMachine = $this->getStateMachine(); - $targetTransition = $stateMachine->getTransitionToState($targetState); + $targetTransition = $stateMachine->getTransitionToState($payment, PaymentTransitions::GRAPH, $targetState); if (null !== $targetTransition) { - $stateMachine->apply($targetTransition); + $stateMachine->apply($payment, PaymentTransitions::GRAPH, $targetTransition); + } + } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof StateMachineFactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); } + + return $this->stateMachineFactory; } } diff --git a/src/Sylius/Component/Core/StateResolver/CheckoutStateResolver.php b/src/Sylius/Component/Core/StateResolver/CheckoutStateResolver.php index d60fc39ab34..c2a136e72d4 100644 --- a/src/Sylius/Component/Core/StateResolver/CheckoutStateResolver.php +++ b/src/Sylius/Component/Core/StateResolver/CheckoutStateResolver.php @@ -14,6 +14,8 @@ namespace Sylius\Component\Core\StateResolver; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\Checker\OrderPaymentMethodSelectionRequirementCheckerInterface; use Sylius\Component\Core\Checker\OrderShippingMethodSelectionRequirementCheckerInterface; use Sylius\Component\Core\Model\OrderInterface; @@ -25,29 +27,47 @@ final class CheckoutStateResolver implements StateResolverInterface { public function __construct( - private FactoryInterface $stateMachineFactory, + private FactoryInterface|StateMachineInterface $stateMachineFactory, private OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, private OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, ) { + trigger_deprecation( + 'sylius/core', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); } public function resolve(BaseOrderInterface $order): void { Assert::isInstanceOf($order, OrderInterface::class); - $stateMachine = $this->stateMachineFactory->get($order, OrderCheckoutTransitions::GRAPH); + $stateMachine = $this->getStateMachine(); if ( !$this->orderShippingMethodSelectionRequirementChecker->isShippingMethodSelectionRequired($order) && - $stateMachine->can(OrderCheckoutTransitions::TRANSITION_SKIP_SHIPPING) + $stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SKIP_SHIPPING) ) { - $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_SKIP_SHIPPING); + $stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SKIP_SHIPPING); } if ( !$this->orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order) && - $stateMachine->can(OrderCheckoutTransitions::TRANSITION_SKIP_PAYMENT) + $stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SKIP_PAYMENT) ) { - $stateMachine->apply(OrderCheckoutTransitions::TRANSITION_SKIP_PAYMENT); + $stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SKIP_PAYMENT); } } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Component/Core/StateResolver/OrderPaymentStateResolver.php b/src/Sylius/Component/Core/StateResolver/OrderPaymentStateResolver.php index 2bf3c8f1070..5357329febc 100644 --- a/src/Sylius/Component/Core/StateResolver/OrderPaymentStateResolver.php +++ b/src/Sylius/Component/Core/StateResolver/OrderPaymentStateResolver.php @@ -15,7 +15,8 @@ use Doctrine\Common\Collections\Collection; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\OrderPaymentTransitions; @@ -26,8 +27,19 @@ final class OrderPaymentStateResolver implements StateResolverInterface { - public function __construct(private FactoryInterface $stateMachineFactory) + public function __construct(private FactoryInterface|StateMachineInterface $stateMachineFactory) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/core', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function resolve(BaseOrderInterface $order): void @@ -35,18 +47,13 @@ public function resolve(BaseOrderInterface $order): void /** @var OrderInterface $order */ Assert::isInstanceOf($order, OrderInterface::class); - $stateMachine = $this->stateMachineFactory->get($order, OrderPaymentTransitions::GRAPH); $targetTransition = $this->getTargetTransition($order); + $stateMachine = $this->getStateMachine(); if (null !== $targetTransition) { - $this->applyTransition($stateMachine, $targetTransition); - } - } - - private function applyTransition(StateMachineInterface $stateMachine, string $transition): void - { - if ($stateMachine->can($transition)) { - $stateMachine->apply($transition); + if ($stateMachine->can($order, OrderPaymentTransitions::GRAPH, $targetTransition)) { + $stateMachine->apply($order, OrderPaymentTransitions::GRAPH, $targetTransition); + } } } @@ -129,4 +136,13 @@ private function getPaymentsWithState(OrderInterface $order, string $state): Col return $payments; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Component/Core/StateResolver/OrderShippingStateResolver.php b/src/Sylius/Component/Core/StateResolver/OrderShippingStateResolver.php index 015b05f013d..15b8ed7bcce 100644 --- a/src/Sylius/Component/Core/StateResolver/OrderShippingStateResolver.php +++ b/src/Sylius/Component/Core/StateResolver/OrderShippingStateResolver.php @@ -14,7 +14,8 @@ namespace Sylius\Component\Core\StateResolver; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\ShipmentInterface; use Sylius\Component\Core\OrderShippingStates; @@ -25,8 +26,19 @@ final class OrderShippingStateResolver implements StateResolverInterface { - public function __construct(private FactoryInterface $stateMachineFactory) + public function __construct(private FactoryInterface|StateMachineInterface $stateMachineFactory) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/core', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function resolve(BaseOrderInterface $order): void @@ -37,15 +49,15 @@ public function resolve(BaseOrderInterface $order): void if (OrderShippingStates::STATE_SHIPPED === $order->getShippingState()) { return; } - /** @var StateMachineInterface $stateMachine */ - $stateMachine = $this->stateMachineFactory->get($order, OrderShippingTransitions::GRAPH); + + $stateMachine = $this->getStateMachine(); if ($this->allShipmentsInStateButOrderStateNotUpdated($order, ShipmentInterface::STATE_SHIPPED, OrderShippingStates::STATE_SHIPPED)) { - $stateMachine->apply(OrderShippingTransitions::TRANSITION_SHIP); + $stateMachine->apply($order, OrderShippingTransitions::GRAPH, OrderShippingTransitions::TRANSITION_SHIP); } if ($this->isPartiallyShippedButOrderStateNotUpdated($order)) { - $stateMachine->apply(OrderShippingTransitions::TRANSITION_PARTIALLY_SHIP); + $stateMachine->apply($order, OrderShippingTransitions::GRAPH, OrderShippingTransitions::TRANSITION_PARTIALLY_SHIP); } } @@ -83,4 +95,13 @@ private function isPartiallyShippedButOrderStateNotUpdated(OrderInterface $order OrderShippingStates::STATE_PARTIALLY_SHIPPED !== $order->getShippingState() ; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Component/Core/StateResolver/OrderStateResolver.php b/src/Sylius/Component/Core/StateResolver/OrderStateResolver.php index 11fba8dfd4e..4a325004ba3 100644 --- a/src/Sylius/Component/Core/StateResolver/OrderStateResolver.php +++ b/src/Sylius/Component/Core/StateResolver/OrderStateResolver.php @@ -14,6 +14,8 @@ namespace Sylius\Component\Core\StateResolver; use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\OrderPaymentStates; use Sylius\Component\Core\OrderShippingStates; @@ -24,17 +26,28 @@ final class OrderStateResolver implements StateResolverInterface { - public function __construct(private FactoryInterface $stateMachineFactory) + public function __construct(private FactoryInterface|StateMachineInterface $stateMachineFactory) { + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/core', + '1.13', + sprintf( + 'Passing an instance of "%s" as the first argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } } public function resolve(BaseOrderInterface $order): void { Assert::isInstanceOf($order, OrderInterface::class); - $stateMachine = $this->stateMachineFactory->get($order, OrderTransitions::GRAPH); + $stateMachine = $this->getStateMachine(); - if ($this->canOrderBeFulfilled($order) && $stateMachine->can(OrderTransitions::TRANSITION_FULFILL)) { - $stateMachine->apply(OrderTransitions::TRANSITION_FULFILL); + if ($this->canOrderBeFulfilled($order) && $stateMachine->can($order, OrderTransitions::GRAPH, OrderTransitions::TRANSITION_FULFILL)) { + $stateMachine->apply($order, OrderTransitions::GRAPH, OrderTransitions::TRANSITION_FULFILL); } } @@ -46,4 +59,13 @@ private function canOrderBeFulfilled(OrderInterface $order): bool OrderShippingStates::STATE_SHIPPED === $order->getShippingState() ; } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Component/Core/Updater/UnpaidOrdersStateUpdater.php b/src/Sylius/Component/Core/Updater/UnpaidOrdersStateUpdater.php index c780203ebd8..b5fa744ed16 100644 --- a/src/Sylius/Component/Core/Updater/UnpaidOrdersStateUpdater.php +++ b/src/Sylius/Component/Core/Updater/UnpaidOrdersStateUpdater.php @@ -16,7 +16,10 @@ use Doctrine\Persistence\ObjectManager; use Psr\Log\LoggerInterface; use SM\Factory\Factory; -use SM\SMException; +use SM\Factory\FactoryInterface; +use Sylius\Abstraction\StateMachine\Exception\StateMachineExecutionException; +use Sylius\Abstraction\StateMachine\StateMachineInterface; +use Sylius\Abstraction\StateMachine\WinzouStateMachineAdapter; use Sylius\Component\Core\Repository\OrderRepositoryInterface; use Sylius\Component\Order\Model\OrderInterface; use Sylius\Component\Order\OrderTransitions; @@ -25,7 +28,7 @@ final class UnpaidOrdersStateUpdater implements UnpaidOrdersStateUpdaterInterfac { public function __construct( private OrderRepositoryInterface $orderRepository, - private Factory $stateMachineFactory, + private Factory|StateMachineInterface $stateMachineFactory, private string $expirationPeriod, private ?LoggerInterface $logger = null, private ?ObjectManager $orderManager = null, @@ -47,6 +50,18 @@ public function __construct( ); } + if ($this->stateMachineFactory instanceof FactoryInterface) { + trigger_deprecation( + 'sylius/core', + '1.13', + sprintf( + 'Passing an instance of "%s" as the second argument is deprecated. It will accept only instances of "%s" in Sylius 2.0.', + FactoryInterface::class, + StateMachineInterface::class, + ), + ); + } + $this->logger = $logger; } @@ -79,13 +94,22 @@ private function findExpiredUnpaidOrders(?int $batchSize): array private function cancelOrder(OrderInterface $expiredUnpaidOrder): void { try { - $stateMachine = $this->stateMachineFactory->get($expiredUnpaidOrder, OrderTransitions::GRAPH); - $stateMachine->apply(OrderTransitions::TRANSITION_CANCEL); - } catch (SMException $e) { + $stateMachine = $this->getStateMachine(); + $stateMachine->apply($expiredUnpaidOrder, OrderTransitions::GRAPH, OrderTransitions::TRANSITION_CANCEL); + } catch (StateMachineExecutionException $e) { $this->logger?->error( sprintf('An error occurred while cancelling unpaid order #%s', $expiredUnpaidOrder->getId()), ['exception' => $e, 'message' => $e->getMessage()], ); } } + + private function getStateMachine(): StateMachineInterface + { + if ($this->stateMachineFactory instanceof FactoryInterface) { + return new WinzouStateMachineAdapter($this->stateMachineFactory); + } + + return $this->stateMachineFactory; + } } diff --git a/src/Sylius/Component/Core/composer.json b/src/Sylius/Component/Core/composer.json index 036be69fed8..4335411f4ff 100644 --- a/src/Sylius/Component/Core/composer.json +++ b/src/Sylius/Component/Core/composer.json @@ -49,6 +49,7 @@ "sylius/resource": "^1.9", "sylius/review": "^1.13", "sylius/shipping": "^1.13", + "sylius/state-machine-abstraction": "^1.13", "sylius/taxation": "^1.13", "sylius/taxonomy": "^1.13", "sylius/user": "^1.13", @@ -82,7 +83,7 @@ }, "autoload-dev": { "psr-4": { - "Sylius\\Component\\Core\\spec\\": "spec/" + "spec\\Sylius\\Component\\Core\\": "spec/" } }, "repositories": [ diff --git a/src/Sylius/Component/Core/spec/Payment/Provider/OrderPaymentProviderSpec.php b/src/Sylius/Component/Core/spec/Payment/Provider/OrderPaymentProviderSpec.php index 25f2ed0fccc..ce646a9da40 100644 --- a/src/Sylius/Component/Core/spec/Payment/Provider/OrderPaymentProviderSpec.php +++ b/src/Sylius/Component/Core/spec/Payment/Provider/OrderPaymentProviderSpec.php @@ -16,6 +16,7 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use SM\Factory\FactoryInterface as StateMachineFactoryInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\Model\PaymentMethodInterface; @@ -25,7 +26,6 @@ use Sylius\Component\Payment\Factory\PaymentFactoryInterface; use Sylius\Component\Payment\PaymentTransitions; use Sylius\Component\Payment\Resolver\DefaultPaymentMethodResolverInterface; -use Sylius\Component\Resource\StateMachine\StateMachineInterface; final class OrderPaymentProviderSpec extends ObjectBehavior { @@ -54,8 +54,18 @@ function it_provides_payment_in_configured_state_with_payment_method_from_last_c PaymentInterface $newPayment, PaymentMethodInterface $paymentMethod, StateMachineFactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineStub $stateMachine, ): void { + $stateMachine->can(PaymentTransitions::TRANSITION_CREATE)->willReturn(true); + $this->setPropertyValue($stateMachine, 'config', [ + 'transitions' => [ + PaymentTransitions::TRANSITION_CREATE => [ + 'from' => [PaymentInterface::STATE_CART], + 'to' => PaymentInterface::STATE_NEW, + ], + ], + ]); + $order->getTotal()->willReturn(1000); $order->getCurrencyCode()->willReturn('USD'); $order->getLastPayment(PaymentInterface::STATE_CANCELLED)->willReturn($lastCancelledPayment); @@ -70,12 +80,45 @@ function it_provides_payment_in_configured_state_with_payment_method_from_last_c $newPayment->setOrder($order)->shouldBeCalled(); $stateMachineFactory->get($newPayment, PaymentTransitions::GRAPH)->willReturn($stateMachine); - $stateMachine->getTransitionToState(PaymentInterface::STATE_NEW)->willReturn(PaymentTransitions::TRANSITION_CREATE); $stateMachine->apply(PaymentTransitions::TRANSITION_CREATE)->shouldBeCalled(); $this->provideOrderPayment($order, PaymentInterface::STATE_NEW)->shouldReturn($newPayment); } + function it_uses_the_new_state_machine_abstraction_if_passed( + DefaultPaymentMethodResolverInterface $defaultPaymentMethodResolver, + PaymentFactoryInterface $paymentFactory, + StateMachineInterface $stateMachine, + OrderInterface $order, + PaymentInterface $lastCancelledPayment, + PaymentInterface $newPayment, + PaymentMethodInterface $paymentMethod, + ): void { + $this->beConstructedWith( + $defaultPaymentMethodResolver, + $paymentFactory, + $stateMachine, + ); + + $order->getTotal()->willReturn(1000); + $order->getCurrencyCode()->willReturn('USD'); + $order->getLastPayment(PaymentInterface::STATE_CANCELLED)->willReturn($lastCancelledPayment); + + $lastCancelledPayment->getMethod()->willReturn($paymentMethod); + + $paymentFactory->createWithAmountAndCurrencyCode(1000, 'USD')->willReturn($newPayment); + $defaultPaymentMethodResolver->getDefaultPaymentMethod($newPayment)->willReturn($paymentMethod); + + $newPayment->setMethod($paymentMethod)->shouldBeCalled(); + $newPayment->getState()->willReturn(PaymentInterface::STATE_CART); + $newPayment->setOrder($order)->shouldBeCalled(); + + $stateMachine->getTransitionToState($newPayment, PaymentTransitions::GRAPH, PaymentInterface::STATE_NEW)->willReturn(PaymentTransitions::TRANSITION_CREATE); + $stateMachine->apply($newPayment, PaymentTransitions::GRAPH, PaymentTransitions::TRANSITION_CREATE)->shouldBeCalled(); + + $this->provideOrderPayment($order, PaymentInterface::STATE_NEW)->shouldReturn($newPayment); + } + function it_provides_payment_in_configured_state_with_payment_method_from_last_failed_payment( DefaultPaymentMethodResolverInterface $defaultPaymentMethodResolver, OrderInterface $order, @@ -84,8 +127,18 @@ function it_provides_payment_in_configured_state_with_payment_method_from_last_f PaymentInterface $newPayment, PaymentMethodInterface $paymentMethod, StateMachineFactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineStub $stateMachine, ): void { + $stateMachine->can(PaymentTransitions::TRANSITION_CREATE)->willReturn(true); + $this->setPropertyValue($stateMachine, 'config', [ + 'transitions' => [ + PaymentTransitions::TRANSITION_CREATE => [ + 'from' => [PaymentInterface::STATE_CART], + 'to' => PaymentInterface::STATE_NEW, + ], + ], + ]); + $order->getTotal()->willReturn(1000); $order->getCurrencyCode()->willReturn('USD'); $order->getLastPayment(PaymentInterface::STATE_CANCELLED)->willReturn(null); @@ -101,7 +154,6 @@ function it_provides_payment_in_configured_state_with_payment_method_from_last_f $newPayment->setOrder($order)->shouldBeCalled(); $stateMachineFactory->get($newPayment, PaymentTransitions::GRAPH)->willReturn($stateMachine); - $stateMachine->getTransitionToState(PaymentInterface::STATE_NEW)->willReturn(PaymentTransitions::TRANSITION_CREATE); $stateMachine->apply(PaymentTransitions::TRANSITION_CREATE)->shouldBeCalled(); $this->provideOrderPayment($order, PaymentInterface::STATE_NEW)->shouldReturn($newPayment); @@ -114,8 +166,18 @@ function it_provides_payment_in_configured_state_with_default_payment_method( PaymentInterface $newPayment, PaymentMethodInterface $paymentMethod, StateMachineFactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineStub $stateMachine, ): void { + $stateMachine->can(PaymentTransitions::TRANSITION_CREATE)->willReturn(true); + $this->setPropertyValue($stateMachine, 'config', [ + 'transitions' => [ + PaymentTransitions::TRANSITION_CREATE => [ + 'from' => [PaymentInterface::STATE_CART], + 'to' => PaymentInterface::STATE_NEW, + ], + ], + ]); + $order->getTotal()->willReturn(1000); $order->getCurrencyCode()->willReturn('USD'); $order->getLastPayment(PaymentInterface::STATE_CANCELLED)->willReturn(null); @@ -130,7 +192,6 @@ function it_provides_payment_in_configured_state_with_default_payment_method( $newPayment->getState()->willReturn(PaymentInterface::STATE_CART); $stateMachineFactory->get($newPayment, PaymentTransitions::GRAPH)->willReturn($stateMachine); - $stateMachine->getTransitionToState(PaymentInterface::STATE_NEW)->willReturn(PaymentTransitions::TRANSITION_CREATE); $stateMachine->apply(PaymentTransitions::TRANSITION_CREATE)->shouldBeCalled(); $this->provideOrderPayment($order, PaymentInterface::STATE_NEW)->shouldReturn($newPayment); @@ -192,4 +253,12 @@ function it_throws_exception_if_payment_method_cannot_be_resolved_for_provided_p ->during('provideOrderPayment', [$order, PaymentInterface::STATE_NEW]) ; } + + private function setPropertyValue(object $object, string $property, mixed $value): void + { + $reflection = new \ReflectionClass(WinzouStateMachineStub::class); + $reflectionProperty = $reflection->getProperty($property); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($object, $value); + } } diff --git a/src/Sylius/Component/Core/spec/Payment/Provider/WinzouStateMachineStub.php b/src/Sylius/Component/Core/spec/Payment/Provider/WinzouStateMachineStub.php new file mode 100644 index 00000000000..ed6305fb1c9 --- /dev/null +++ b/src/Sylius/Component/Core/spec/Payment/Provider/WinzouStateMachineStub.php @@ -0,0 +1,21 @@ +isPaymentMethodSelectionRequired($order)->willReturn(false); @@ -63,12 +64,33 @@ function it_applies_transition_skip_shipping_and_skip_payment_if_shipping_method $this->resolve($order); } + function it_uses_the_new_state_machine_abstraction_if_passed( + StateMachineInterface $stateMachine, + OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, + OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, + OrderInterface $order, + ): void { + $this->beConstructedWith($stateMachine, $orderPaymentMethodSelectionRequirementChecker, $orderShippingMethodSelectionRequirementChecker); + + $orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)->willReturn(false); + + $orderShippingMethodSelectionRequirementChecker->isShippingMethodSelectionRequired($order)->willReturn(false); + + $stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SKIP_SHIPPING)->willReturn(true); + $stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SKIP_SHIPPING)->shouldBeCalled(); + + $stateMachine->can($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SKIP_PAYMENT)->willReturn(true); + $stateMachine->apply($order, OrderCheckoutTransitions::GRAPH, OrderCheckoutTransitions::TRANSITION_SKIP_PAYMENT)->shouldBeCalled(); + + $this->resolve($order); + } + function it_applies_transition_skip_shipping_if_shipping_method_selection_is_not_required_and_this_transition_is_possible( FactoryInterface $stateMachineFactory, OrderInterface $order, OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)->willReturn(true); @@ -90,7 +112,7 @@ function it_does_not_apply_skip_shipping_transition_if_shipping_method_selection OrderInterface $order, OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)->willReturn(true); @@ -112,7 +134,7 @@ function it_does_not_apply_skip_shipping_transition_if_it_is_not_possible( OrderInterface $order, OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)->willReturn(true); @@ -134,7 +156,7 @@ function it_applies_transition_skip_payment_if_payment_method_selection_is_not_r OrderInterface $order, OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)->willReturn(false); @@ -156,7 +178,7 @@ function it_does_not_apply_skip_payment_transition_if_payment_method_selection_i OrderInterface $order, OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)->willReturn(true); @@ -178,7 +200,7 @@ function it_does_not_apply_skip_payment_transition_if_transition_skip_payment_is OrderInterface $order, OrderPaymentMethodSelectionRequirementCheckerInterface $orderPaymentMethodSelectionRequirementChecker, OrderShippingMethodSelectionRequirementCheckerInterface $orderShippingMethodSelectionRequirementChecker, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $orderPaymentMethodSelectionRequirementChecker->isPaymentMethodSelectionRequired($order)->willReturn(true); diff --git a/src/Sylius/Component/Core/spec/StateResolver/OrderPaymentStateResolverSpec.php b/src/Sylius/Component/Core/spec/StateResolver/OrderPaymentStateResolverSpec.php index 0e0821a67d5..43cb14751df 100644 --- a/src/Sylius/Component/Core/spec/StateResolver/OrderPaymentStateResolverSpec.php +++ b/src/Sylius/Component/Core/spec/StateResolver/OrderPaymentStateResolverSpec.php @@ -16,7 +16,8 @@ use Doctrine\Common\Collections\ArrayCollection; use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\PaymentInterface; use Sylius\Component\Core\OrderPaymentTransitions; @@ -36,7 +37,7 @@ function it_implements_an_order_state_resolver_interface(): void function it_marks_an_order_as_refunded_if_all_its_payments_are_refunded( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, PaymentInterface $secondPayment, @@ -59,9 +60,34 @@ function it_marks_an_order_as_refunded_if_all_its_payments_are_refunded( $this->resolve($order); } + function it_uses_the_new_state_machine_if_passed( + StateMachineInterface $stateMachine, + OrderInterface $order, + PaymentInterface $firstPayment, + PaymentInterface $secondPayment, + ): void { + $this->beConstructedWith($stateMachine); + + $firstPayment->getAmount()->willReturn(6000); + $firstPayment->getState()->willReturn(PaymentInterface::STATE_REFUNDED); + $secondPayment->getAmount()->willReturn(4000); + $secondPayment->getState()->willReturn(PaymentInterface::STATE_REFUNDED); + + $order + ->getPayments() + ->willReturn(new ArrayCollection([$firstPayment->getWrappedObject(), $secondPayment->getWrappedObject()])) + ; + $order->getTotal()->willReturn(10000); + + $stateMachine->can($order, OrderPaymentTransitions::GRAPH, OrderPaymentTransitions::TRANSITION_REFUND)->willReturn(true); + $stateMachine->apply($order, OrderPaymentTransitions::GRAPH, OrderPaymentTransitions::TRANSITION_REFUND)->shouldBeCalled(); + + $this->resolve($order); + } + function it_marks_an_order_as_refunded_if_its_payments_are_refunded_or_failed_but_at_least_one_is_refunded( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, PaymentInterface $secondPayment, @@ -86,7 +112,7 @@ function it_marks_an_order_as_refunded_if_its_payments_are_refunded_or_failed_bu function it_marks_an_order_as_paid_if_fully_paid( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $payment, ): void { @@ -107,7 +133,7 @@ function it_marks_an_order_as_paid_if_fully_paid( function it_marks_an_order_as_paid_if_it_does_not_have_any_payments( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, ) { $order->getPayments()->willReturn(new ArrayCollection([])); @@ -122,7 +148,7 @@ function it_marks_an_order_as_paid_if_it_does_not_have_any_payments( function it_marks_an_order_as_paid_if_fully_paid_even_if_previous_payment_was_failed( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, PaymentInterface $secondPayment, @@ -146,7 +172,7 @@ function it_marks_an_order_as_paid_if_fully_paid_even_if_previous_payment_was_fa function it_marks_an_order_as_partially_refunded_if_one_of_the_payment_is_refunded( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, PaymentInterface $secondPayment, @@ -171,7 +197,7 @@ function it_marks_an_order_as_partially_refunded_if_one_of_the_payment_is_refund function it_marks_an_order_as_completed_if_fully_paid_multiple_payments( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, PaymentInterface $secondPayment, @@ -196,7 +222,7 @@ function it_marks_an_order_as_completed_if_fully_paid_multiple_payments( function it_marks_an_order_as_partially_paid_if_one_of_the_payment_is_processing( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, PaymentInterface $secondPayment, @@ -221,7 +247,7 @@ function it_marks_an_order_as_partially_paid_if_one_of_the_payment_is_processing function it_marks_an_order_as_authorized_if_all_its_payments_are_authorized( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, PaymentInterface $secondPayment, @@ -246,7 +272,7 @@ function it_marks_an_order_as_authorized_if_all_its_payments_are_authorized( function it_marks_an_order_as_partially_authorized_if_one_of_the_payments_is_processing_and_one_of_the_payments_is_authorized( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, PaymentInterface $secondPayment, @@ -271,7 +297,7 @@ function it_marks_an_order_as_partially_authorized_if_one_of_the_payments_is_pro function it_marks_an_order_as_awaiting_payment_if_payments_is_processing( FactoryInterface $stateMachineFactory, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, OrderInterface $order, PaymentInterface $firstPayment, ): void { diff --git a/src/Sylius/Component/Core/spec/StateResolver/OrderShippingStateResolverSpec.php b/src/Sylius/Component/Core/spec/StateResolver/OrderShippingStateResolverSpec.php index 3acea331411..4163914a92f 100644 --- a/src/Sylius/Component/Core/spec/StateResolver/OrderShippingStateResolverSpec.php +++ b/src/Sylius/Component/Core/spec/StateResolver/OrderShippingStateResolverSpec.php @@ -16,7 +16,8 @@ use Doctrine\Common\Collections\ArrayCollection; use PhpSpec\ObjectBehavior; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\Model\ShipmentInterface; use Sylius\Component\Core\OrderShippingStates; @@ -40,7 +41,7 @@ function it_marks_an_order_as_shipped_if_all_shipments_delivered( OrderInterface $order, ShipmentInterface $shipment1, ShipmentInterface $shipment2, - StateMachineInterface $orderStateMachine, + WinzouStateMachineInterface $orderStateMachine, ): void { $shipments = new ArrayCollection(); $shipments->add($shipment1->getWrappedObject()); @@ -58,10 +59,33 @@ function it_marks_an_order_as_shipped_if_all_shipments_delivered( $this->resolve($order); } + function it_uses_the_new_state_machine_abstraction_if_passed( + StateMachineInterface $orderStateMachine, + OrderInterface $order, + ShipmentInterface $shipment1, + ShipmentInterface $shipment2, + ): void { + $this->beConstructedWith($orderStateMachine); + + $shipments = new ArrayCollection(); + $shipments->add($shipment1->getWrappedObject()); + $shipments->add($shipment2->getWrappedObject()); + + $order->getShipments()->willReturn($shipments); + $order->getShippingState()->willReturn(OrderShippingStates::STATE_READY); + + $shipment1->getState()->willReturn(ShipmentInterface::STATE_SHIPPED); + $shipment2->getState()->willReturn(ShipmentInterface::STATE_SHIPPED); + + $orderStateMachine->apply($order, OrderShippingTransitions::GRAPH, OrderShippingTransitions::TRANSITION_SHIP)->shouldBeCalled(); + + $this->resolve($order); + } + function it_marks_an_order_as_shipped_if_there_are_no_shipments_to_deliver( FactoryInterface $stateMachineFactory, OrderInterface $order, - StateMachineInterface $orderStateMachine, + WinzouStateMachineInterface $orderStateMachine, ): void { $order->getShipments()->willReturn(new ArrayCollection()); $order->getShippingState()->willReturn(OrderShippingStates::STATE_READY); @@ -77,7 +101,7 @@ function it_marks_an_order_as_partially_shipped_if_some_shipments_are_delivered( OrderInterface $order, ShipmentInterface $shipment1, ShipmentInterface $shipment2, - StateMachineInterface $orderStateMachine, + WinzouStateMachineInterface $orderStateMachine, ): void { $shipments = new ArrayCollection(); $shipments->add($shipment1->getWrappedObject()); @@ -100,7 +124,7 @@ function it_does_not_mark_an_order_if_it_is_already_in_this_shipping_state( OrderInterface $order, ShipmentInterface $shipment1, ShipmentInterface $shipment2, - StateMachineInterface $orderStateMachine, + WinzouStateMachineInterface $orderStateMachine, ): void { $shipments = new ArrayCollection(); $shipments->add($shipment1->getWrappedObject()); diff --git a/src/Sylius/Component/Core/spec/StateResolver/OrderStateResolverSpec.php b/src/Sylius/Component/Core/spec/StateResolver/OrderStateResolverSpec.php index ce582ece5ee..a973098e861 100644 --- a/src/Sylius/Component/Core/spec/StateResolver/OrderStateResolverSpec.php +++ b/src/Sylius/Component/Core/spec/StateResolver/OrderStateResolverSpec.php @@ -16,7 +16,8 @@ use PhpSpec\ObjectBehavior; use Prophecy\Argument; use SM\Factory\FactoryInterface; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Component\Core\Model\OrderInterface; use Sylius\Component\Core\OrderPaymentStates; use Sylius\Component\Core\OrderShippingStates; @@ -38,7 +39,7 @@ function it_implements_a_state_resolver_interface(): void function it_marks_an_order_as_fulfilled_when_its_paid_for_and_has_been_shipped( FactoryInterface $stateMachineFactory, OrderInterface $order, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $order->getShippingState()->willReturn(OrderShippingStates::STATE_SHIPPED); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PAID); @@ -51,10 +52,25 @@ function it_marks_an_order_as_fulfilled_when_its_paid_for_and_has_been_shipped( $this->resolve($order); } + function it_uses_the_new_state_machine_abstraction_if_passed( + StateMachineInterface $stateMachine, + OrderInterface $order, + ): void { + $this->beConstructedWith($stateMachine); + + $order->getShippingState()->willReturn(OrderShippingStates::STATE_SHIPPED); + $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PAID); + + $stateMachine->can($order, OrderTransitions::GRAPH, OrderTransitions::TRANSITION_FULFILL)->willReturn(true); + $stateMachine->apply($order, OrderTransitions::GRAPH, OrderTransitions::TRANSITION_FULFILL)->shouldBeCalled(); + + $this->resolve($order); + } + function it_marks_an_order_as_fulfilled_when_its_partially_refunded_and_has_been_shipped( FactoryInterface $stateMachineFactory, OrderInterface $order, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $order->getShippingState()->willReturn(OrderShippingStates::STATE_SHIPPED); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PARTIALLY_REFUNDED); @@ -70,7 +86,7 @@ function it_marks_an_order_as_fulfilled_when_its_partially_refunded_and_has_been function it_does_not_mark_an_order_as_fulfilled_when_it_has_been_paid_but_not_shipped( FactoryInterface $stateMachineFactory, OrderInterface $order, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $order->getShippingState()->willReturn(Argument::not(OrderShippingStates::STATE_SHIPPED)); $order->getPaymentState()->willReturn(OrderPaymentStates::STATE_PAID); @@ -86,7 +102,7 @@ function it_does_not_mark_an_order_as_fulfilled_when_it_has_been_paid_but_not_sh function it_does_not_mark_an_order_as_fulfilled_when_it_has_been_shipped_but_not_paid( FactoryInterface $stateMachineFactory, OrderInterface $order, - StateMachineInterface $stateMachine, + WinzouStateMachineInterface $stateMachine, ): void { $order->getShippingState()->willReturn(OrderShippingStates::STATE_SHIPPED); $order->getPaymentState()->willReturn(Argument::notIn([OrderPaymentStates::STATE_PAID, OrderPaymentStates::STATE_PARTIALLY_REFUNDED])); diff --git a/src/Sylius/Component/Core/spec/Updater/UnpaidOrdersStateUpdaterSpec.php b/src/Sylius/Component/Core/spec/Updater/UnpaidOrdersStateUpdaterSpec.php index 79eed85906d..60713d2ad56 100644 --- a/src/Sylius/Component/Core/spec/Updater/UnpaidOrdersStateUpdaterSpec.php +++ b/src/Sylius/Component/Core/spec/Updater/UnpaidOrdersStateUpdaterSpec.php @@ -19,7 +19,8 @@ use Psr\Log\LoggerInterface; use SM\Factory\Factory; use SM\SMException; -use SM\StateMachine\StateMachineInterface; +use SM\StateMachine\StateMachineInterface as WinzouStateMachineInterface; +use Sylius\Abstraction\StateMachine\StateMachineInterface; use Sylius\Component\Core\Repository\OrderRepositoryInterface; use Sylius\Component\Core\Updater\UnpaidOrdersStateUpdaterInterface; use Sylius\Component\Order\Model\OrderInterface; @@ -55,9 +56,9 @@ function it_cancels_unpaid_orders( OrderInterface $secondOrder, OrderInterface $thirdOrder, OrderRepositoryInterface $orderRepository, - StateMachineInterface $firstOrderStateMachine, - StateMachineInterface $secondOrderStateMachine, - StateMachineInterface $thirdOrderStateMachine, + WinzouStateMachineInterface $firstOrderStateMachine, + WinzouStateMachineInterface $secondOrderStateMachine, + WinzouStateMachineInterface $thirdOrderStateMachine, ): void { $orderRepository->findOrdersUnpaidSince(Argument::type(\DateTimeInterface::class), 100)->willReturn( [$firstOrder, $secondOrder], @@ -79,14 +80,47 @@ function it_cancels_unpaid_orders( $this->cancel(); } + function it_uses_the_new_state_machine_abstraction_if_passed( + StateMachineInterface $stateMachine, + ObjectManager $objectManager, + OrderInterface $firstOrder, + OrderInterface $secondOrder, + OrderInterface $thirdOrder, + OrderRepositoryInterface $orderRepository, + ): void { + $this->beConstructedWith( + $orderRepository, + $stateMachine, + '10 months', + null, + $objectManager, + 100, + ); + + $orderRepository->findOrdersUnpaidSince(Argument::type(\DateTimeInterface::class), 100)->willReturn( + [$firstOrder, $secondOrder], + [$thirdOrder], + [], + ); + + $objectManager->flush()->shouldBeCalledTimes(2); + $objectManager->clear()->shouldBeCalledTimes(2); + + $stateMachine->apply($firstOrder, 'sylius_order', OrderTransitions::TRANSITION_CANCEL)->shouldBeCalled(); + $stateMachine->apply($secondOrder, 'sylius_order', OrderTransitions::TRANSITION_CANCEL)->shouldBeCalled(); + $stateMachine->apply($thirdOrder, 'sylius_order', OrderTransitions::TRANSITION_CANCEL)->shouldBeCalled(); + + $this->cancel(); + } + function it_wont_stop_cancelling_unpaid_orders_on_exception_for_a_single_order_and_logs_error( Factory $stateMachineFactory, ObjectManager $objectManager, OrderInterface $firstOrder, OrderInterface $secondOrder, OrderRepositoryInterface $orderRepository, - StateMachineInterface $firstOrderStateMachine, - StateMachineInterface $secondOrderStateMachine, + WinzouStateMachineInterface $firstOrderStateMachine, + WinzouStateMachineInterface $secondOrderStateMachine, LoggerInterface $logger, ): void { $orderRepository->findOrdersUnpaidSince(Argument::type(\DateTimeInterface::class), 100)->willReturn( @@ -121,8 +155,8 @@ function it_wont_stop_cancelling_unpaid_orders_on_exception_for_a_single_order_a OrderInterface $firstOrder, OrderInterface $secondOrder, OrderRepositoryInterface $orderRepository, - StateMachineInterface $firstOrderStateMachine, - StateMachineInterface $secondOrderStateMachine, + WinzouStateMachineInterface $firstOrderStateMachine, + WinzouStateMachineInterface $secondOrderStateMachine, ): void { $this->beConstructedWith($orderRepository, $stateMachineFactory, '10 months', null, $objectManager); @@ -150,8 +184,8 @@ function it_wont_batch_processing_unpaid_orders_if_object_manager_is_not_set( OrderInterface $firstOrder, OrderInterface $secondOrder, OrderRepositoryInterface $orderRepository, - StateMachineInterface $firstOrderStateMachine, - StateMachineInterface $secondOrderStateMachine, + WinzouStateMachineInterface $firstOrderStateMachine, + WinzouStateMachineInterface $secondOrderStateMachine, ): void { $this->beConstructedWith($orderRepository, $stateMachineFactory, '10 months', null, null, 100);